initial import from pitchfork-0.5.5
authorJoshua Wise <joshua@h2so4.joshuawise.com>
Sat, 21 Nov 2009 22:39:18 +0000 (17:39 -0500)
committerJoshua Wise <joshua@h2so4.joshuawise.com>
Sat, 21 Nov 2009 22:39:18 +0000 (17:39 -0500)
206 files changed:
.htaccess [new file with mode: 0644]
COPYING [new file with mode: 0644]
CREDITS [new file with mode: 0644]
ChangeLog [new file with mode: 0644]
README [new file with mode: 0644]
config/.htaccess [new file with mode: 0644]
config/.svnignore [new file with mode: 0644]
doc/INSTALL [new file with mode: 0644]
doc/UPGRADING [new file with mode: 0644]
doc/pitchfork.conf [new file with mode: 0644]
doc/pitchfork_domain.conf [new file with mode: 0644]
images/add.png [new file with mode: 0644]
images/audio.png [new file with mode: 0644]
images/crop.png [new file with mode: 0644]
images/enabled_15.gif [new file with mode: 0644]
images/folder.png [new file with mode: 0644]
images/gmpc-no-cover-thumb.png [new file with mode: 0644]
images/gmpc-no-cover.png [new file with mode: 0644]
images/info.png [new file with mode: 0644]
images/left.png [new file with mode: 0644]
images/lock.png [new file with mode: 0644]
images/media-album.png [new file with mode: 0644]
images/media-artist.png [new file with mode: 0644]
images/next.png [new file with mode: 0644]
images/pause.png [new file with mode: 0644]
images/play.png [new file with mode: 0644]
images/playlist.png [new file with mode: 0644]
images/preferences.png [new file with mode: 0644]
images/previous.png [new file with mode: 0644]
images/quit.png [new file with mode: 0644]
images/remove.png [new file with mode: 0644]
images/right.png [new file with mode: 0644]
inc/JSON.php [new file with mode: 0644]
inc/Net/MPD.php [new file with mode: 0644]
inc/Net/MPD/Common.php [new file with mode: 0644]
inc/Net/Net/MPD/Admin.php [new file with mode: 0644]
inc/Net/Net/MPD/Database.php [new file with mode: 0644]
inc/Net/Net/MPD/Playback.php [new file with mode: 0644]
inc/Net/Net/MPD/Playlist.php [new file with mode: 0644]
inc/Net/package.xml [new file with mode: 0644]
inc/base.php [new file with mode: 0644]
inc/function_test.php [new file with mode: 0644]
index.php [new file with mode: 0644]
jorbis/build.xml [new file with mode: 0644]
jorbis/jorbis-pitchfork.jar [new file with mode: 0644]
jorbis/src/JOrbisPlayer.java [new file with mode: 0644]
jorbis/src/com/jcraft/jogg/Buffer.java [new file with mode: 0644]
jorbis/src/com/jcraft/jogg/Packet.java [new file with mode: 0644]
jorbis/src/com/jcraft/jogg/Page.java [new file with mode: 0644]
jorbis/src/com/jcraft/jogg/StreamState.java [new file with mode: 0644]
jorbis/src/com/jcraft/jogg/SyncState.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/AllocChain.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Block.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/ChainingExample.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/CodeBook.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Comment.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/DecodeExample.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Drft.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/DspState.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/EncodeAuxNearestMatch.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/EncodeAuxThreshMatch.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Floor0.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Floor1.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/FuncFloor.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/FuncMapping.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/FuncResidue.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/FuncTime.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Info.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/InfoMode.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/JOrbisException.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Lookup.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Lpc.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Lsp.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Mapping0.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Mdct.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/PsyInfo.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/PsyLook.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Residue0.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Residue1.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Residue2.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/StaticCodeBook.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/Time0.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/VorbisFile.java [new file with mode: 0644]
jorbis/src/com/jcraft/jorbis/VorbisFile.java.new [new file with mode: 0644]
lang/de.js [new file with mode: 0644]
lang/de.php [new file with mode: 0644]
lang/en.js [new file with mode: 0644]
lang/en.php [new file with mode: 0644]
lang/eu.js [new file with mode: 0644]
lang/eu.php [new file with mode: 0644]
lang/fr.js [new file with mode: 0644]
lang/fr.php [new file with mode: 0644]
lang/master.php [new file with mode: 0644]
player/command.php [new file with mode: 0644]
player/config.php [new file with mode: 0644]
player/index.php [new file with mode: 0644]
player/login.php [new file with mode: 0644]
player/metadata.php [new file with mode: 0644]
player/metadata_cover.php [new file with mode: 0644]
player/nowplaying.php [new file with mode: 0644]
player/openstrands.php [new file with mode: 0644]
player/preferences.js.php [new file with mode: 0644]
std/base.css [new file with mode: 0644]
std/browser.js [new file with mode: 0644]
std/collection.js [new file with mode: 0644]
std/command.js [new file with mode: 0644]
std/keyboard.js [new file with mode: 0644]
std/metadata.js [new file with mode: 0644]
std/playlist.js [new file with mode: 0644]
std/plsearch.js [new file with mode: 0644]
std/quickadd.js [new file with mode: 0644]
std/streaming.js [new file with mode: 0644]
std/toolkit.js [new file with mode: 0644]
theme/dark/images/COPYING [new file with mode: 0644]
theme/dark/images/add_15.png [new file with mode: 0644]
theme/dark/images/audio_li.png [new file with mode: 0644]
theme/dark/images/close.png [new file with mode: 0644]
theme/dark/images/close_15.png [new file with mode: 0644]
theme/dark/images/crop_30.png [new file with mode: 0644]
theme/dark/images/disabled_15.png [new file with mode: 0644]
theme/dark/images/enabled_15.png [new file with mode: 0644]
theme/dark/images/folder_30.png [new file with mode: 0644]
theme/dark/images/folder_li.png [new file with mode: 0644]
theme/dark/images/follow-playing-no.orig.png [new file with mode: 0644]
theme/dark/images/follow-playing-no.png [new file with mode: 0644]
theme/dark/images/follow-playing-yes.orig.png [new file with mode: 0644]
theme/dark/images/follow-playing-yes.png [new file with mode: 0644]
theme/dark/images/jump-to-current.orig.png [new file with mode: 0644]
theme/dark/images/jump-to-current.png [new file with mode: 0644]
theme/dark/images/left_15.png [new file with mode: 0644]
theme/dark/images/media-album_li.png [new file with mode: 0644]
theme/dark/images/media-artist_li.png [new file with mode: 0644]
theme/dark/images/next_25.png [new file with mode: 0644]
theme/dark/images/pause_25.png [new file with mode: 0644]
theme/dark/images/play_25.png [new file with mode: 0644]
theme/dark/images/playlist_li.png [new file with mode: 0644]
theme/dark/images/previous_25.png [new file with mode: 0644]
theme/dark/images/remove_30.png [new file with mode: 0644]
theme/dark/images/right_15.png [new file with mode: 0644]
theme/dark/images/stop_25.png [new file with mode: 0644]
theme/dark/images/working.gif [new file with mode: 0644]
theme/dark/theme.css [new file with mode: 0644]
theme/dark/theme.js [new file with mode: 0644]
theme/default/images/add_15.png [new file with mode: 0644]
theme/default/images/audio_li.png [new file with mode: 0644]
theme/default/images/close.png [new file with mode: 0644]
theme/default/images/close_15.png [new file with mode: 0644]
theme/default/images/crop_30.png [new file with mode: 0644]
theme/default/images/disabled_15.png [new file with mode: 0644]
theme/default/images/enabled_15.png [new file with mode: 0644]
theme/default/images/folder_30.png [new file with mode: 0644]
theme/default/images/folder_li.png [new file with mode: 0644]
theme/default/images/follow-playing-no.png [new file with mode: 0644]
theme/default/images/follow-playing-yes.png [new file with mode: 0644]
theme/default/images/jump-to-current.png [new file with mode: 0644]
theme/default/images/left_15.png [new file with mode: 0644]
theme/default/images/media-album_li.png [new file with mode: 0644]
theme/default/images/media-artist_li.png [new file with mode: 0644]
theme/default/images/next_25.png [new file with mode: 0644]
theme/default/images/pause_25.png [new file with mode: 0644]
theme/default/images/play_25.png [new file with mode: 0644]
theme/default/images/playlist_li.png [new file with mode: 0644]
theme/default/images/previous_25.png [new file with mode: 0644]
theme/default/images/remove_30.png [new file with mode: 0644]
theme/default/images/right_15.png [new file with mode: 0644]
theme/default/images/stop_25.png [new file with mode: 0644]
theme/default/images/working.gif [new file with mode: 0644]
theme/default/theme.css [new file with mode: 0644]
theme/default/theme.js [new file with mode: 0644]
theme/thexy/images/activity.gif [new file with mode: 0644]
theme/thexy/images/add.png [new file with mode: 0644]
theme/thexy/images/album.png [new file with mode: 0644]
theme/thexy/images/artist.png [new file with mode: 0644]
theme/thexy/images/back.png [new file with mode: 0644]
theme/thexy/images/bkg.jpg [new file with mode: 0644]
theme/thexy/images/browse_bkg.png [new file with mode: 0644]
theme/thexy/images/browse_media.png [new file with mode: 0644]
theme/thexy/images/content_bkg.jpg [new file with mode: 0644]
theme/thexy/images/control_bkg.jpg [new file with mode: 0644]
theme/thexy/images/crop_selection.png [new file with mode: 0644]
theme/thexy/images/default_album_art.jpg [new file with mode: 0644]
theme/thexy/images/directory.png [new file with mode: 0644]
theme/thexy/images/disabled.png [new file with mode: 0644]
theme/thexy/images/enabled.png [new file with mode: 0644]
theme/thexy/images/file.png [new file with mode: 0644]
theme/thexy/images/fileclose.png [new file with mode: 0644]
theme/thexy/images/forward.png [new file with mode: 0644]
theme/thexy/images/jump.png [new file with mode: 0644]
theme/thexy/images/lcd.jpg [new file with mode: 0644]
theme/thexy/images/next.jpg [new file with mode: 0644]
theme/thexy/images/next_light.jpg [new file with mode: 0644]
theme/thexy/images/pause.jpg [new file with mode: 0644]
theme/thexy/images/pause_light.jpg [new file with mode: 0644]
theme/thexy/images/play.jpg [new file with mode: 0644]
theme/thexy/images/play_light.jpg [new file with mode: 0644]
theme/thexy/images/playlist.png [new file with mode: 0644]
theme/thexy/images/previous.jpg [new file with mode: 0644]
theme/thexy/images/previous_light.jpg [new file with mode: 0644]
theme/thexy/images/remove_selection.png [new file with mode: 0644]
theme/thexy/images/shadowAlpha.png [new file with mode: 0644]
theme/thexy/images/slider.png [new file with mode: 0644]
theme/thexy/images/stop.jpg [new file with mode: 0644]
theme/thexy/images/stop_light.jpg [new file with mode: 0644]
theme/thexy/images/tools_bkg.jpg [new file with mode: 0644]
theme/thexy/theme.css [new file with mode: 0644]
theme/thexy/theme.js [new file with mode: 0644]

diff --git a/.htaccess b/.htaccess
new file mode 100644 (file)
index 0000000..4e05a6c
--- /dev/null
+++ b/.htaccess
@@ -0,0 +1 @@
+Order Deny,Allow
diff --git a/COPYING b/COPYING
new file mode 100644 (file)
index 0000000..a102c93
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,285 @@
+Copyright Roger Bystroem <roger@remiss.org>
+Released under GPL v.2
+
+This applies to everything not specified otherwise in CREDITS or elsewhere
+
+                   GNU GENERAL PUBLIC LICENSE
+                      Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
+ 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 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 Lesser 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
diff --git a/CREDITS b/CREDITS
new file mode 100644 (file)
index 0000000..9f98e33
--- /dev/null
+++ b/CREDITS
@@ -0,0 +1,16 @@
+CREDITS
+
+Icons stolen from (art.gnome.org): 
+ Silvestre Herrera (Nuovo images)
+ Steven Garrity (Tango, see tango authors)
+
+media-*
+ -Gnome Default Theme
+
+Albumart from amazon, copyrights belong to their respective copyright-holders
+
+Net_MPD (http://pear.php.net/package/Net_MPD/):
+ Graham Christensen 
+
+Jorbis (used for ogg playback)
+http://www.jcraft.com/jorbis/
diff --git a/ChangeLog b/ChangeLog
new file mode 100644 (file)
index 0000000..47fbf32
--- /dev/null
+++ b/ChangeLog
@@ -0,0 +1,150 @@
+
+Pitchfork ChangeLog
+
+------------------------------------------------------------------------------
+
+Changes 0.5.5 (18.01.2008)
+ * pitchfork now reports errors from mpd
+ * New thexy theme from Gianni Chiappetta
+
+Changes - 0.5.4 (25.09.2007)
+ * Cleanup css files so it's easier to make new themes
+ * Plausible fix for segfault/unable to assign complex types to properties
+ issues with albumart
+ * Menu options finally look decent
+ * Make dirlist init not fail if library is empty
+ * Removed a few useless debug statements
+ * Tried to switch to a negative page when pagination was enabled, fix for #81 
+ * Typo in multi add made it only search for last parameter, found by oliver
+ * French translation by Djanil
+ * Basque translation by Xabier Ezpeleta
+ * Fix for slow loading of page one when there's a big playlist (#86)
+ * Switched to html controls for streaming applet and also improved the applet
+ a bit
+
+Changes - 0.5.3 (24.06.2007)
+ * Improved amazon album search to give more correct hit
+ * Added more to album name as described in #62
+ * Show title under artist/album browser instead of filename
+ * Show Artist - Title in browser title bar
+ * Fix bug with xml2array function causing lyrics to loose newlines
+ * Make streaming applet autoplay again and also when coming out of hiding
+ * Make applet stop playing immediately when mpd stops playing to make it seem
+ more responsive
+ * Fix race condition with sidebar and album art
+ * Update big albumart when it's open
+ * Translation support
+ * Show full file path with search results
+ * Make the player control area slightly shorter
+ * German translation by Christian Fischer
+
+
+Changes - 0.5.2 (24.04.2007)
+ * Support for changing position display to remaining
+ * Workaround for konqueror repaint issue (bug #46)
+ * Added pagination support
+ * Added ability to search playlist
+ * Short info/test display on configure screen
+ * Follow and jump to currently playing song option
+ * Added dark theme
+ * Encrypt/hash pitchfork login password
+ * Fixed bug in Hashmap.remove. It would return the container object if it was
+ the first element in the list. 
+ * Added nofollow and noindex to html meta tags. Consider adding pitchfork to
+ a robots.txt file on your server.
+ * Fix caching issue in Opera on the stream player
+ * Add background property to the stream plugin and try to close audio device
+   when stream stops
+ * Workaround for broken xml-declaration on lyricwiki.org
+ * Link to adding/editing lyric on lyricwiki.org
+
+
+Changes - 0.5.1 (21.03.2007)
+ * Switched to JSON in commands
+ * Added ogg streaming support with jorbisplayer
+ * Improved speed of plchanges
+ * Add warnings when something goes wrong with locking of metadata-files
+ * Don't use PHP shorttags
+ * Cleanups
+ * Fix bug where albumart wouldn't show when mime_content_type wasn't
+ available
+ * Added license blurp to all files to avoid any issues 
+
+
+Changes - 0.5.0 (28.02.2007)
+ * Improved theming support
+ * Add theme choice to configure screen
+ * Album review/description
+ * Fix MPD port specification bug
+ * Fix bug with filename in lyrics files
+ * Add option for including stop button
+ * Clean up include stuff (also fixes an issue with initial config screen in opera)
+ * Fix font-issues
+ * Allow to change what should be displayed in the playlist
+ * Add forced lyrics reload
+ * Fixes for safari specific issues
+ * Metadata for album review and description
+ * Changed look of lyrics screen and added refetch button
+
+Changes - 0.4.1 (10.02.2007)
+ * Minor threshold on move in playlist
+ * Fix bug in directory browser
+ * Playlistsupport
+ * Optimizations for selection and playlist
+ * Test on notfound value in album.txt and try again if it is some while ago
+ * adding playlists
+
+Changes - 0.4.0 (08.02.2007)
+ * multiple move support
+ * speed up plchanges
+ * Get changes based only on id/pos
+ * Update DB support
+ * Bugfixes on directory browser
+ * Autoplay on add if pl empty
+ * Search function 
+
+Changes - 0.3.1 (07.02.2007)
+ * Fix rendering error in browsers other than gecko-based
+
+Changes - 0.3.0 (04.02.2007)
+ * Added parent when doubleclicked in dirlist
+ * Don't add whole directory if return is pressed in qa and nothing there
+ * album art
+ * lyrics
+ * mv TODO ChangeLog
+ * new_state == stop => remove currently playing
+ * config => password protection, delay time
+ * Directory browsing based on artist etc
+ * PHP to javascript preferences (delay time won't be implemented until this is fixed)
+ * Split css into base/color-stuff
+
+Changes - 0.2.0 (29.01.2007)
+ * Pool for playlist elements as gecko don't seem to free them
+ * server settings (output devices, random, repeat, xfade) drop-down menu
+ * close folder if clicking on folder icon when folder is open
+ * Speedup of playlist changes and initial playlist loading
+ * Searching in directory listing
+ * Close can't be on escape, some browsers closes open connections to server
+ * Adjust slider when clicking somewhere on it
+ * Run slider callback when moving (250ms threshold)
+ * Fix race-condition, dirlist started building before elements were ready
+ * Several concurrent information requests, should never be more than one looping....
+       * reconnect on broken set_timeout
+ * Quick Add
+ * Clean upp all display data when clearing playlist/stopping
+
+Changes - 0.1.0rc1/2 (21.01.2007)
+ * icon to indicate song/folder in dirlist
+ * organization
+   - double click moves right, seperate enque button (hot key for add)
+   - double click adds, single click opens
+ * .htaccess/stuff.conf file
+ * Use utf-8
+ * When removing song before currently playing, showing of currently playing is removed 
+ * Hi-light on prev/play/pause/next ..done
+ * coloured background around the "player" area (mockup.png)
+ * seems to fail on selecting playing song sometimes
+ * fixed position on "player" area? 
+ * moving something up in the list causes jumping on response from server (the moved is probably placed wrongly so that it jumps one up on response from server)
+   - "Fixed" - when dropped on something it probably should land there....
+   - FIXED! Oh happy days!
diff --git a/README b/README
new file mode 100644 (file)
index 0000000..642c401
--- /dev/null
+++ b/README
@@ -0,0 +1,23 @@
+
+1. Installation
+       see doc/INSTALL
+
+2. Requirements
+    * PHP 5.1.3 or newer
+    * PHP-Pear
+    * DOM2 capable browser (firefox, konqueror, opera, safari, etc)
+    * MPD 0.12.0 or later 
+
+
+3. Shortcuts
+       Key shortcuts in browsers are a mess, some don't send on certain keys and others
+       don't allow you to cancel default action, therefore the shortcuts are a little weird.
+       I've tried to use what seems most logical and works in most browsers, that also means
+       there's a slight workaround some places
+
+       - Previous song:        j
+       - Pause/Play:           k
+       - Next song:            l
+       - Close directory:      Ctrl+Shift+X
+       - Quick search:         Ctrl+Shift+S or Ctrl+Alt+S
+       - Jumpt to current song: space
diff --git a/config/.htaccess b/config/.htaccess
new file mode 100644 (file)
index 0000000..8eea256
--- /dev/null
@@ -0,0 +1 @@
+Order Allow,Deny
diff --git a/config/.svnignore b/config/.svnignore
new file mode 100644 (file)
index 0000000..35a2cc3
--- /dev/null
@@ -0,0 +1,2 @@
+config.xml
+metadata
diff --git a/doc/INSTALL b/doc/INSTALL
new file mode 100644 (file)
index 0000000..75a5f62
--- /dev/null
@@ -0,0 +1,53 @@
+Installation instructions
+
+1. Put either the pitchfork.conf or pitchfork_domain.conf file, found in this doc/ directory,
+   in /etc/http/conf.d (or where your extra config-files are) 
+   and make sure they specify the correct directories for pitchfork.
+2. Apache needs to have write access to the config directory: 
+   - Go to where you installed pitchfork
+   - chown apache config (or whatever user apache is run as, can be 'nobody')
+     to find out who the apache-user is check output from "ps auxww | grep httpd || ps auxww|grep apache"
+3. Reload/restart apache
+4. Open your web-browser to where you installed apache and answer the questions ;)
+
+Apache access logs
+The client by default requests information every second from the server, this means that a lot of 
+stuff will end up in your access log. To disable logging of the these requests you will have to 
+change the CustomLog directive in apache. It should be sufficient to add env=!pitchforknolog after it 
+(if you have used the given .conf file)
+
+Example customlog directive:
+CustomLog logs/access_log combined env=!pitchforknolog
+
+For more information about logs: 
+http://httpd.apache.org/docs/2.0/logs.html#accesslog
+
+Required PHP-Modules:
+Usually shipped with the php-distribution, but listed here for modular php-implementations.
+ - Hash
+ - SimpleXML
+ - JSON (not required, but highly recommended)
+
+
+When you have problems: 
+
+ * apache refuses to start and you see "invalid command php_flag"
+ * The only thing you see is some PHP-code
+       - You have to enable PHP in httpd.conf, make sure you have 
+       "LoadModule php5_module modules/libphp5.so" somewhere in the config file
+
+ * All you see is a blank page OR
+ * Warning: require_once(PEAR/Exception.php) [function.require-once]: 
+     failed to open stream: No such file or 
+     directory in <something>/pitchfork/inc/Net/MPD.php
+        - You need to install php-pear (might also be called PEAR-PEAR)
+ * Allowed memory size of .. bytes exhausted in apache error log
+       - You need to increase the allowed memory size in php.ini, 
+         set memory_limit to at least 64M
+ * No metadata (ie. images/lyrics/etc) is showing
+       - make sure allow_url_fopen is set to on in php.ini and that the config/ directory 
+         is writeable by apache
+
+
diff --git a/doc/UPGRADING b/doc/UPGRADING
new file mode 100644 (file)
index 0000000..6e2f195
--- /dev/null
@@ -0,0 +1,7 @@
+
+Should be sufficient to just extract the tarball and copy the old
+config directory over to the new directory. (cp -A pitchfork.old/config/* pitchfork/config/)
+
+Remember to make sure apache has write permission to the config directory.
+
+
diff --git a/doc/pitchfork.conf b/doc/pitchfork.conf
new file mode 100644 (file)
index 0000000..e3151f2
--- /dev/null
@@ -0,0 +1,15 @@
+Alias /pitchfork /var/www/pitchfork
+<Directory /var/www/pitchfork>
+       DirectoryIndex index.php
+       Options -Indexes
+       AllowOverride all
+       AddType application/x-httpd-php .php
+       php_flag magic_quotes_gpc off
+       php_flag magic_quotes_runtime off
+       php_flag display_errors off
+       php_flag log_errors on
+       # switch comment marks on the two items to disable access logging 
+       # completely for pitchfork. See INSTALL
+       SetEnvIf Request_URI "player/command\.php" pitchforknolog 
+       #SetEnv pitchforknolog
+</Directory>
diff --git a/doc/pitchfork_domain.conf b/doc/pitchfork_domain.conf
new file mode 100644 (file)
index 0000000..a523e8f
--- /dev/null
@@ -0,0 +1,18 @@
+<VirtualHost *:80>
+       # you will probably want to change the two following settings:
+       ServerName pitchfork
+       DocumentRoot /var/www/pitchfork
+       <Directory /var/www/pitchfork>
+               AllowOverride all
+       </Directory>
+       DirectoryIndex index.php
+       Options -Indexes
+       php_flag magic_quotes_gpc off
+       php_flag display_errors off
+       php_flag log_errors on
+       AddType application/x-httpd-php .php
+       # switch comment marks on the two items to disable access logging 
+       # completely for pitchfork. See INSTALL
+       SetEnvIf Request_URI "player/command\.php" pitchforknolog 
+       #SetEnv pitchforknolog
+</VirtualHost>
diff --git a/images/add.png b/images/add.png
new file mode 100644 (file)
index 0000000..4857fa7
Binary files /dev/null and b/images/add.png differ
diff --git a/images/audio.png b/images/audio.png
new file mode 100644 (file)
index 0000000..f4ad2d4
Binary files /dev/null and b/images/audio.png differ
diff --git a/images/crop.png b/images/crop.png
new file mode 100644 (file)
index 0000000..73b7164
Binary files /dev/null and b/images/crop.png differ
diff --git a/images/enabled_15.gif b/images/enabled_15.gif
new file mode 100644 (file)
index 0000000..609485f
Binary files /dev/null and b/images/enabled_15.gif differ
diff --git a/images/folder.png b/images/folder.png
new file mode 100644 (file)
index 0000000..4d4b9c8
Binary files /dev/null and b/images/folder.png differ
diff --git a/images/gmpc-no-cover-thumb.png b/images/gmpc-no-cover-thumb.png
new file mode 100644 (file)
index 0000000..d8fb76e
Binary files /dev/null and b/images/gmpc-no-cover-thumb.png differ
diff --git a/images/gmpc-no-cover.png b/images/gmpc-no-cover.png
new file mode 100644 (file)
index 0000000..2b800b3
Binary files /dev/null and b/images/gmpc-no-cover.png differ
diff --git a/images/info.png b/images/info.png
new file mode 100644 (file)
index 0000000..f81de9a
Binary files /dev/null and b/images/info.png differ
diff --git a/images/left.png b/images/left.png
new file mode 100644 (file)
index 0000000..3e0bba8
Binary files /dev/null and b/images/left.png differ
diff --git a/images/lock.png b/images/lock.png
new file mode 100644 (file)
index 0000000..e7546d3
Binary files /dev/null and b/images/lock.png differ
diff --git a/images/media-album.png b/images/media-album.png
new file mode 100644 (file)
index 0000000..b514bf7
Binary files /dev/null and b/images/media-album.png differ
diff --git a/images/media-artist.png b/images/media-artist.png
new file mode 100644 (file)
index 0000000..20dfeb1
Binary files /dev/null and b/images/media-artist.png differ
diff --git a/images/next.png b/images/next.png
new file mode 100644 (file)
index 0000000..7c1dbdb
Binary files /dev/null and b/images/next.png differ
diff --git a/images/pause.png b/images/pause.png
new file mode 100644 (file)
index 0000000..e4f0699
Binary files /dev/null and b/images/pause.png differ
diff --git a/images/play.png b/images/play.png
new file mode 100644 (file)
index 0000000..66202cc
Binary files /dev/null and b/images/play.png differ
diff --git a/images/playlist.png b/images/playlist.png
new file mode 100644 (file)
index 0000000..d8975b0
Binary files /dev/null and b/images/playlist.png differ
diff --git a/images/preferences.png b/images/preferences.png
new file mode 100644 (file)
index 0000000..869cc3b
Binary files /dev/null and b/images/preferences.png differ
diff --git a/images/previous.png b/images/previous.png
new file mode 100644 (file)
index 0000000..1b4f722
Binary files /dev/null and b/images/previous.png differ
diff --git a/images/quit.png b/images/quit.png
new file mode 100644 (file)
index 0000000..2a1fba0
Binary files /dev/null and b/images/quit.png differ
diff --git a/images/remove.png b/images/remove.png
new file mode 100644 (file)
index 0000000..3042194
Binary files /dev/null and b/images/remove.png differ
diff --git a/images/right.png b/images/right.png
new file mode 100644 (file)
index 0000000..ac8ed0c
Binary files /dev/null and b/images/right.png differ
diff --git a/inc/JSON.php b/inc/JSON.php
new file mode 100644 (file)
index 0000000..37fe123
--- /dev/null
@@ -0,0 +1,808 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Converts to and from JSON format.
+ *
+ * JSON (JavaScript Object Notation) is a lightweight data-interchange
+ * format. It is easy for humans to read and write. It is easy for machines
+ * to parse and generate. It is based on a subset of the JavaScript
+ * Programming Language, Standard ECMA-262 3rd Edition - December 1999.
+ * This feature can also be found in  Python. JSON is a text format that is
+ * completely language independent but uses conventions that are familiar
+ * to programmers of the C-family of languages, including C, C++, C#, Java,
+ * JavaScript, Perl, TCL, and many others. These properties make JSON an
+ * ideal data-interchange language.
+ *
+ * This package provides a simple encoder and decoder for JSON notation. It
+ * is intended for use with client-side Javascript applications that make
+ * use of HTTPRequest to perform server communication functions - data can
+ * be encoded into JSON notation for use in a client-side javascript, or
+ * decoded from incoming Javascript requests. JSON format is native to
+ * Javascript, and can be directly eval()'ed with no further parsing
+ * overhead
+ *
+ * All strings should be in ASCII or UTF-8 format!
+ *
+ * LICENSE: Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met: Redistributions of source code must retain the
+ * above copyright notice, this list of conditions and the following
+ * disclaimer. Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following disclaimer
+ * in the documentation and/or other materials provided with the
+ * distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN
+ * NO EVENT SHALL CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+ * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+ * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+ * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+ * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+ * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+ * DAMAGE.
+ *
+ * @category
+ * @package     Services_JSON
+ * @author      Michal Migurski <mike-json@teczno.com>
+ * @author      Matt Knapp <mdknapp[at]gmail[dot]com>
+ * @author      Brett Stimmerman <brettstimmerman[at]gmail[dot]com>
+ * @copyright   2005 Michal Migurski
+ * @version     CVS: $Id: JSON.php,v 1.31 2006/06/28 05:54:17 migurski Exp $
+ * @license     http://www.opensource.org/licenses/bsd-license.php
+ * @link        http://pear.php.net/pepr/pepr-proposal-show.php?id=198
+ */
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_SLICE',   1);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_STR',  2);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_ARR',  3);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_OBJ',  4);
+
+/**
+ * Marker constant for Services_JSON::decode(), used to flag stack state
+ */
+define('SERVICES_JSON_IN_CMT', 5);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_LOOSE_TYPE', 16);
+
+/**
+ * Behavior switch for Services_JSON::decode()
+ */
+define('SERVICES_JSON_SUPPRESS_ERRORS', 32);
+
+/**
+ * Converts to and from JSON format.
+ *
+ * Brief example of use:
+ *
+ * <code>
+ * // create a new instance of Services_JSON
+ * $json = new Services_JSON();
+ *
+ * // convert a complexe value to JSON notation, and send it to the browser
+ * $value = array('foo', 'bar', array(1, 2, 'baz'), array(3, array(4)));
+ * $output = $json->encode($value);
+ *
+ * print($output);
+ * // prints: ["foo","bar",[1,2,"baz"],[3,[4]]]
+ *
+ * // accept incoming POST data, assumed to be in JSON notation
+ * $input = file_get_contents('php://input', 1000000);
+ * $value = $json->decode($input);
+ * </code>
+ */
+class Services_JSON
+{
+   /**
+    * constructs a new JSON instance
+    *
+    * @param    int     $use    object behavior flags; combine with boolean-OR
+    *
+    *                           possible values:
+    *                           - SERVICES_JSON_LOOSE_TYPE:  loose typing.
+    *                                   "{...}" syntax creates associative arrays
+    *                                   instead of objects in decode().
+    *                           - SERVICES_JSON_SUPPRESS_ERRORS:  error suppression.
+    *                                   Values which can't be encoded (e.g. resources)
+    *                                   appear as NULL instead of throwing errors.
+    *                                   By default, a deeply-nested resource will
+    *                                   bubble up with an error, so all return values
+    *                                   from encode() should be checked with isError()
+    */
+    function Services_JSON($use = 0)
+    {
+        $this->use = $use;
+    }
+
+   /**
+    * convert a string from one UTF-16 char to one UTF-8 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf16  UTF-16 character
+    * @return   string  UTF-8 character
+    * @access   private
+    */
+    function utf162utf8($utf16)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf16, 'UTF-8', 'UTF-16');
+        }
+
+        $bytes = (ord($utf16{0}) << 8) | ord($utf16{1});
+
+        switch(true) {
+            case ((0x7F & $bytes) == $bytes):
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x7F & $bytes);
+
+            case (0x07FF & $bytes) == $bytes:
+                // return a 2-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xC0 | (($bytes >> 6) & 0x1F))
+                     . chr(0x80 | ($bytes & 0x3F));
+
+            case (0xFFFF & $bytes) == $bytes:
+                // return a 3-byte UTF-8 character
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0xE0 | (($bytes >> 12) & 0x0F))
+                     . chr(0x80 | (($bytes >> 6) & 0x3F))
+                     . chr(0x80 | ($bytes & 0x3F));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+   /**
+    * convert a string from one UTF-8 char to one UTF-16 char
+    *
+    * Normally should be handled by mb_convert_encoding, but
+    * provides a slower PHP-only method for installations
+    * that lack the multibye string extension.
+    *
+    * @param    string  $utf8   UTF-8 character
+    * @return   string  UTF-16 character
+    * @access   private
+    */
+    function utf82utf16($utf8)
+    {
+        // oh please oh please oh please oh please oh please
+        if(function_exists('mb_convert_encoding')) {
+            return mb_convert_encoding($utf8, 'UTF-16', 'UTF-8');
+        }
+
+        switch(strlen($utf8)) {
+            case 1:
+                // this case should never be reached, because we are in ASCII range
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return $utf8;
+
+            case 2:
+                // return a UTF-16 character from a 2-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr(0x07 & (ord($utf8{0}) >> 2))
+                     . chr((0xC0 & (ord($utf8{0}) << 6))
+                         | (0x3F & ord($utf8{1})));
+
+            case 3:
+                // return a UTF-16 character from a 3-byte UTF-8 char
+                // see: http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                return chr((0xF0 & (ord($utf8{0}) << 4))
+                         | (0x0F & (ord($utf8{1}) >> 2)))
+                     . chr((0xC0 & (ord($utf8{1}) << 6))
+                         | (0x7F & ord($utf8{2})));
+        }
+
+        // ignoring UTF-32 for now, sorry
+        return '';
+    }
+
+
+    /* modified for pitchfork to use echo instead of recursing over all elements and returning a *large* string */
+    /* could probably remove the conversion thing as input is utf-8 and output is utf-8 */
+   /**
+    * encodes an arbitrary variable into JSON format
+    *
+    * @param    mixed   $var    any number, boolean, string, array, or object to be encoded.
+    *                           see argument 1 to Services_JSON() above for array-parsing behavior.
+    *                           if var is a strng, note that encode() always expects it
+    *                           to be in ASCII or UTF-8 format!
+    *
+    * @return   mixed   JSON string representation of input var or an error if a problem occurs
+    * @access   public
+    */
+    function encode($var)
+    {
+        switch (gettype($var)) {
+            case 'boolean':
+                echo $var ? 'true' : 'false';
+                return;
+
+            case 'NULL':
+                echo 'null';
+                return;
+
+            case 'integer':
+                echo (int) $var;
+                return;
+
+            case 'double':
+            case 'float':
+                echo (float) $var;
+                return;
+
+            case 'string':
+                // STRINGS ARE EXPECTED TO BE IN ASCII OR UTF-8 FORMAT
+                $ascii = '';
+                $strlen_var = strlen($var);
+
+               /*
+                * Iterate over every character in the string,
+                * escaping with a slash or encoding to UTF-8 where necessary
+                */
+                for ($c = 0; $c < $strlen_var; ++$c) {
+
+                    $ord_var_c = ord($var{$c});
+
+                    switch (true) {
+                        case $ord_var_c == 0x08:
+                            $ascii .= '\b';
+                            break;
+                        case $ord_var_c == 0x09:
+                            $ascii .= '\t';
+                            break;
+                        case $ord_var_c == 0x0A:
+                            $ascii .= '\n';
+                            break;
+                        case $ord_var_c == 0x0C:
+                            $ascii .= '\f';
+                            break;
+                        case $ord_var_c == 0x0D:
+                            $ascii .= '\r';
+                            break;
+
+                        case $ord_var_c == 0x22:
+                        case $ord_var_c == 0x2F:
+                        case $ord_var_c == 0x5C:
+                            // double quote, slash, slosh
+                            $ascii .= '\\'.$var{$c};
+                            break;
+
+                        case (($ord_var_c >= 0x20) && ($ord_var_c <= 0x7F)):
+                            // characters U-00000000 - U-0000007F (same as ASCII)
+                            $ascii .= $var{$c};
+                            break;
+
+                        case (($ord_var_c & 0xE0) == 0xC0):
+                            // characters U-00000080 - U-000007FF, mask 110XXXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c, ord($var{$c + 1}));
+                            $c += 1;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF0) == 0xE0):
+                            // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}));
+                            $c += 2;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xF8) == 0xF0):
+                            // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}));
+                            $c += 3;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFC) == 0xF8):
+                            // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}));
+                            $c += 4;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+
+                        case (($ord_var_c & 0xFE) == 0xFC):
+                            // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                            // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                            $char = pack('C*', $ord_var_c,
+                                         ord($var{$c + 1}),
+                                         ord($var{$c + 2}),
+                                         ord($var{$c + 3}),
+                                         ord($var{$c + 4}),
+                                         ord($var{$c + 5}));
+                            $c += 5;
+                            $utf16 = $this->utf82utf16($char);
+                            $ascii .= sprintf('\u%04s', bin2hex($utf16));
+                            break;
+                    }
+                }
+
+                echo '"'.$ascii.'"';
+                return;
+
+            case 'array':
+               /*
+                * As per JSON spec if any array key is not an integer
+                * we must treat the the whole array as an object. We
+                * also try to catch a sparsely populated associative
+                * array with numeric keys here because some JS engines
+                * will create an array with empty indexes up to
+                * max_index which can cause memory issues and because
+                * the keys, which may be relevant, will be remapped
+                * otherwise.
+                *
+                * As per the ECMA and JSON specification an object may
+                * have any string as a property. Unfortunately due to
+                * a hole in the ECMA specification if the key is a
+                * ECMA reserved word or starts with a digit the
+                * parameter is only accessible using ECMAScript's
+                * bracket notation.
+                */
+
+                // treat as a JSON object
+                if (is_array($var) && count($var) && (array_keys($var) !== range(0, sizeof($var) - 1))) {
+                    echo "{";
+                    $first = true;
+                    foreach($var as $k => $v) {
+                        if($first) $first = false;
+                        else echo ",";
+                        $this->name_value($k, $v);
+                    }
+
+                    echo '}';
+                    return;
+                }
+
+                // treat it like a regular array
+                echo "[";
+                $first = true;
+                foreach($var as $e) {
+                    if($first) $first = false;
+                    else echo ",";
+                    $this->encode($e);
+                }
+                echo "]";
+                return;
+
+            case 'object':
+                echo "{";
+                $first = true;
+                foreach($var as $k => $v) {
+                    if($first) 
+                        $first = false;
+                    else echo ",";
+                    $this->name_value($k, $v);
+                }
+
+                echo '}';
+                return;
+
+            default:
+                echo ($this->use & SERVICES_JSON_SUPPRESS_ERRORS)
+                    ? 'null'
+                    : 'error encoding';
+                return;
+        }
+    }
+
+   /**
+    * array-walking function for use in generating JSON-formatted name-value pairs
+    *
+    * @param    string  $name   name of key to use
+    * @param    mixed   $value  reference to an array element to be encoded
+    *
+    * @return   string  JSON-formatted name-value pair, like '"name":value'
+    * @access   private
+    */
+    function name_value($name, $value)
+    {
+        $this->encode(strval($name));
+        echo ':';
+        $this->encode($value);
+    }
+
+   /**
+    * reduce a string by removing leading and trailing comments and whitespace
+    *
+    * @param    $str    string      string value to strip of comments and whitespace
+    *
+    * @return   string  string value stripped of comments and whitespace
+    * @access   private
+    */
+    function reduce_string($str)
+    {
+        $str = preg_replace(array(
+
+                // eliminate single line comments in '// ...' form
+                '#^\s*//(.+)$#m',
+
+                // eliminate multi-line comments in '/* ... */' form, at start of string
+                '#^\s*/\*(.+)\*/#Us',
+
+                // eliminate multi-line comments in '/* ... */' form, at end of string
+                '#/\*(.+)\*/\s*$#Us'
+
+            ), '', $str);
+
+        // eliminate extraneous space
+        return trim($str);
+    }
+
+   /**
+    * decodes a JSON string into appropriate variable
+    *
+    * @param    string  $str    JSON-formatted string
+    *
+    * @return   mixed   number, boolean, string, array, or object
+    *                   corresponding to given JSON input string.
+    *                   See argument 1 to Services_JSON() above for object-output behavior.
+    *                   Note that decode() always returns strings
+    *                   in ASCII or UTF-8 format!
+    * @access   public
+    */
+    function decode($str)
+    {
+        $str = $this->reduce_string($str);
+
+        switch (strtolower($str)) {
+            case 'true':
+                return true;
+
+            case 'false':
+                return false;
+
+            case 'null':
+                return null;
+
+            default:
+                $m = array();
+
+                if (is_numeric($str)) {
+                    // Lookie-loo, it's a number
+
+                    // This would work on its own, but I'm trying to be
+                    // good about returning integers where appropriate:
+                    // return (float)$str;
+
+                    // Return float or int, as appropriate
+                    return ((float)$str == (integer)$str)
+                        ? (integer)$str
+                        : (float)$str;
+
+                } elseif (preg_match('/^("|\').*(\1)$/s', $str, $m) && $m[1] == $m[2]) {
+                    // STRINGS RETURNED IN UTF-8 FORMAT
+                    $delim = substr($str, 0, 1);
+                    $chrs = substr($str, 1, -1);
+                    $utf8 = '';
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c < $strlen_chrs; ++$c) {
+
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+                        $ord_chrs_c = ord($chrs{$c});
+
+                        switch (true) {
+                            case $substr_chrs_c_2 == '\b':
+                                $utf8 .= chr(0x08);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\t':
+                                $utf8 .= chr(0x09);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\n':
+                                $utf8 .= chr(0x0A);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\f':
+                                $utf8 .= chr(0x0C);
+                                ++$c;
+                                break;
+                            case $substr_chrs_c_2 == '\r':
+                                $utf8 .= chr(0x0D);
+                                ++$c;
+                                break;
+
+                            case $substr_chrs_c_2 == '\\"':
+                            case $substr_chrs_c_2 == '\\\'':
+                            case $substr_chrs_c_2 == '\\\\':
+                            case $substr_chrs_c_2 == '\\/':
+                                if (($delim == '"' && $substr_chrs_c_2 != '\\\'') ||
+                                   ($delim == "'" && $substr_chrs_c_2 != '\\"')) {
+                                    $utf8 .= $chrs{++$c};
+                                }
+                                break;
+
+                            case preg_match('/\\\u[0-9A-F]{4}/i', substr($chrs, $c, 6)):
+                                // single, escaped unicode character
+                                $utf16 = chr(hexdec(substr($chrs, ($c + 2), 2)))
+                                       . chr(hexdec(substr($chrs, ($c + 4), 2)));
+                                $utf8 .= $this->utf162utf8($utf16);
+                                $c += 5;
+                                break;
+
+                            case ($ord_chrs_c >= 0x20) && ($ord_chrs_c <= 0x7F):
+                                $utf8 .= $chrs{$c};
+                                break;
+
+                            case ($ord_chrs_c & 0xE0) == 0xC0:
+                                // characters U-00000080 - U-000007FF, mask 110XXXXX
+                                //see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 2);
+                                ++$c;
+                                break;
+
+                            case ($ord_chrs_c & 0xF0) == 0xE0:
+                                // characters U-00000800 - U-0000FFFF, mask 1110XXXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 3);
+                                $c += 2;
+                                break;
+
+                            case ($ord_chrs_c & 0xF8) == 0xF0:
+                                // characters U-00010000 - U-001FFFFF, mask 11110XXX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 4);
+                                $c += 3;
+                                break;
+
+                            case ($ord_chrs_c & 0xFC) == 0xF8:
+                                // characters U-00200000 - U-03FFFFFF, mask 111110XX
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 5);
+                                $c += 4;
+                                break;
+
+                            case ($ord_chrs_c & 0xFE) == 0xFC:
+                                // characters U-04000000 - U-7FFFFFFF, mask 1111110X
+                                // see http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8
+                                $utf8 .= substr($chrs, $c, 6);
+                                $c += 5;
+                                break;
+
+                        }
+
+                    }
+
+                    return $utf8;
+
+                } elseif (preg_match('/^\[.*\]$/s', $str) || preg_match('/^\{.*\}$/s', $str)) {
+                    // array, or object notation
+
+                    if ($str{0} == '[') {
+                        $stk = array(SERVICES_JSON_IN_ARR);
+                        $arr = array();
+                    } else {
+                        if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = array();
+                        } else {
+                            $stk = array(SERVICES_JSON_IN_OBJ);
+                            $obj = new stdClass();
+                        }
+                    }
+
+                    array_push($stk, array('what'  => SERVICES_JSON_SLICE,
+                                           'where' => 0,
+                                           'delim' => false));
+
+                    $chrs = substr($str, 1, -1);
+                    $chrs = $this->reduce_string($chrs);
+
+                    if ($chrs == '') {
+                        if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                            return $arr;
+
+                        } else {
+                            return $obj;
+
+                        }
+                    }
+
+                    //print("\nparsing {$chrs}\n");
+
+                    $strlen_chrs = strlen($chrs);
+
+                    for ($c = 0; $c <= $strlen_chrs; ++$c) {
+
+                        $top = end($stk);
+                        $substr_chrs_c_2 = substr($chrs, $c, 2);
+
+                        if (($c == $strlen_chrs) || (($chrs{$c} == ',') && ($top['what'] == SERVICES_JSON_SLICE))) {
+                            // found a comma that is not inside a string, array, etc.,
+                            // OR we've reached the end of the character list
+                            $slice = substr($chrs, $top['where'], ($c - $top['where']));
+                            array_push($stk, array('what' => SERVICES_JSON_SLICE, 'where' => ($c + 1), 'delim' => false));
+                            //print("Found split at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                            if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                                // we are in an array, so just push an element onto the stack
+                                array_push($arr, $this->decode($slice));
+
+                            } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                                // we are in an object, so figure
+                                // out the property name and set an
+                                // element in an associative array,
+                                // for now
+                                $parts = array();
+                                
+                                if (preg_match('/^\s*(["\'].*[^\\\]["\'])\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // "name":value pair
+                                    $key = $this->decode($parts[1]);
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                } elseif (preg_match('/^\s*(\w+)\s*:\s*(\S.*),?$/Uis', $slice, $parts)) {
+                                    // name:value pair, where name is unquoted
+                                    $key = $parts[1];
+                                    $val = $this->decode($parts[2]);
+
+                                    if ($this->use & SERVICES_JSON_LOOSE_TYPE) {
+                                        $obj[$key] = $val;
+                                    } else {
+                                        $obj->$key = $val;
+                                    }
+                                }
+
+                            }
+
+                        } elseif ((($chrs{$c} == '"') || ($chrs{$c} == "'")) && ($top['what'] != SERVICES_JSON_IN_STR)) {
+                            // found a quote, and we are not inside a string
+                            array_push($stk, array('what' => SERVICES_JSON_IN_STR, 'where' => $c, 'delim' => $chrs{$c}));
+                            //print("Found start of string at {$c}\n");
+
+                        } elseif (($chrs{$c} == $top['delim']) &&
+                                 ($top['what'] == SERVICES_JSON_IN_STR) &&
+                                 ((strlen(substr($chrs, 0, $c)) - strlen(rtrim(substr($chrs, 0, $c), '\\'))) % 2 != 1)) {
+                            // found a quote, we're in a string, and it's not escaped
+                            // we know that it's not escaped becase there is _not_ an
+                            // odd number of backslashes at the end of the string so far
+                            array_pop($stk);
+                            //print("Found end of string at {$c}: ".substr($chrs, $top['where'], (1 + 1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '[') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-bracket, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_ARR, 'where' => $c, 'delim' => false));
+                            //print("Found start of array at {$c}\n");
+
+                        } elseif (($chrs{$c} == ']') && ($top['what'] == SERVICES_JSON_IN_ARR)) {
+                            // found a right-bracket, and we're in an array
+                            array_pop($stk);
+                            //print("Found end of array at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($chrs{$c} == '{') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a left-brace, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_OBJ, 'where' => $c, 'delim' => false));
+                            //print("Found start of object at {$c}\n");
+
+                        } elseif (($chrs{$c} == '}') && ($top['what'] == SERVICES_JSON_IN_OBJ)) {
+                            // found a right-brace, and we're in an object
+                            array_pop($stk);
+                            //print("Found end of object at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        } elseif (($substr_chrs_c_2 == '/*') &&
+                                 in_array($top['what'], array(SERVICES_JSON_SLICE, SERVICES_JSON_IN_ARR, SERVICES_JSON_IN_OBJ))) {
+                            // found a comment start, and we are in an array, object, or slice
+                            array_push($stk, array('what' => SERVICES_JSON_IN_CMT, 'where' => $c, 'delim' => false));
+                            $c++;
+                            //print("Found start of comment at {$c}\n");
+
+                        } elseif (($substr_chrs_c_2 == '*/') && ($top['what'] == SERVICES_JSON_IN_CMT)) {
+                            // found a comment end, and we're in one now
+                            array_pop($stk);
+                            $c++;
+
+                            for ($i = $top['where']; $i <= $c; ++$i)
+                                $chrs = substr_replace($chrs, ' ', $i, 1);
+
+                            //print("Found end of comment at {$c}: ".substr($chrs, $top['where'], (1 + $c - $top['where']))."\n");
+
+                        }
+
+                    }
+
+                    if (reset($stk) == SERVICES_JSON_IN_ARR) {
+                        return $arr;
+
+                    } elseif (reset($stk) == SERVICES_JSON_IN_OBJ) {
+                        return $obj;
+
+                    }
+
+                }
+        }
+    }
+
+    /**
+     * @todo Ultimately, this should just call PEAR::isError()
+     */
+    function isError($data, $code = null)
+    {
+        if (class_exists('pear')) {
+            return PEAR::isError($data, $code);
+        } elseif (is_object($data) && (get_class($data) == 'services_json_error' ||
+                                 is_subclass_of($data, 'services_json_error'))) {
+            return true;
+        }
+
+        return false;
+    }
+}
+
+if (class_exists('PEAR_Error')) {
+
+    class Services_JSON_Error extends PEAR_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+            parent::PEAR_Error($message, $code, $mode, $options, $userinfo);
+        }
+    }
+
+} else {
+
+    /**
+     * @todo Ultimately, this class shall be descended from PEAR_Error
+     */
+    class Services_JSON_Error
+    {
+        function Services_JSON_Error($message = 'unknown error', $code = null,
+                                     $mode = null, $options = null, $userinfo = null)
+        {
+
+        }
+    }
+
+}
+    
+?>
diff --git a/inc/Net/MPD.php b/inc/Net/MPD.php
new file mode 100644 (file)
index 0000000..df84823
--- /dev/null
@@ -0,0 +1,119 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+
+
+require_once 'PEAR/Exception.php';
+require_once 'MPD/Common.php';
+
+
+/**
+ * Central class for the Music Player Daemon objects
+ *
+ * Used to utilize the functionality of the provided classes
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+class Net_MPD
+{
+    /**
+     * The Net_MPD_Admin object
+     */
+    public $Admin;
+
+    /**
+     * The Net_MPD_Common object
+     */
+    public $Common;
+
+    /**
+     * The Net_MPD_Database object
+     */
+    public $Database;
+
+    /**
+     * The Net_MPD_Playback object
+     */
+    public $Playback;
+
+    /**
+     * The Net_MPD_Playlist object
+     */
+    public $Playlist;
+
+    /**
+     * Creates new instances of objects
+     * @param $host Host to connect to, optional (default: localhost)
+     * @param $port Port to connect to, optional (default: 6600)
+     * @param $pass Pass to connect to, optional (default: null)
+     * @return object or false on failure
+     */
+    function __construct($host = 'localhost', $port = 6600, $pass = null)
+    {
+        $this->Admin    = self::factory('Admin'   , $host, $port, $pass);
+        $this->Common   = self::factory('Common'  , $host, $port, $pass);
+        $this->Database = self::factory('Database', $host, $port, $pass);
+        $this->Playback = self::factory('Playback', $host, $port, $pass);
+        $this->Playlist = self::factory('Playlist', $host, $port, $pass);
+    }
+
+    /**
+     * Creates new instances of objects
+     * @param $class Class to initiate, with out Net_MPD_$class
+     * @param $host Host to connect to, optional (default: localhost)
+     * @param $port Port to connect to, optional (default: 6600)
+     * @param $pass Pass to connect to, optional (default: null)
+     * @return object or false on failure
+     */
+    public static function factory($class, $host = 'localhost', $port = 6600, $pass = null)
+    {
+        $class = ucfirst(strtolower($class));
+
+        if (!self::_loadClass($class)) {
+            return false;
+        }
+
+        $class = 'Net_MPD_' . $class;
+        $obj = new $class($host, $port, $pass);
+
+        return $obj;
+    }
+
+    /**
+    * Loads the class
+    * @param $class Class to include, with out Net_MPD_
+    * @return bool
+    */
+    protected static function _loadClass($class)
+    {
+        if (class_exists('Net_MPD_' . $class)) {
+            return true;
+        }
+        require_once 'Net/MPD/' . $class . '.php';
+        return true;
+    }
+}
+?>
\ No newline at end of file
diff --git a/inc/Net/MPD/Common.php b/inc/Net/MPD/Common.php
new file mode 100644 (file)
index 0000000..d921154
--- /dev/null
@@ -0,0 +1,492 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+
+/**
+ * API for the common peices of Music Player Daemon commands
+ *
+ * Used for basic interaction and output handeling, as well as
+ * several standard commands.
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+class Net_MPD_Common
+{
+    //Connection & Write Errors
+    const CONNECTION_NOT_OPENED   = 100;
+    const CONNECTION_FAILED       = 102;
+    const WRITE_FAILED            = 103;
+
+    //MPD Errors
+    const ACK_NOT_LIST            = 1;
+    const ACK_ARG                 = 2;
+    const ACK_PASSWORD            = 3;
+    const ACK_PERMISSION          = 4;
+    const ACK_UNKOWN              = 5;
+    const ACK_NO_EXIST            = 50;
+    const ACK_PLAYLIST_MAX        = 51;
+    const ACK_SYSTEM              = 52;
+    const ACK_PLAYLIST_LOAD       = 53;
+    const ACK_UPDATE_ALREADY      = 54;
+    const ACK_PLAYER_SYNC         = 55;
+    const ACK_EXIST               = 56;
+    const ACK_COMMAND_FAILED      = -100;
+
+    //MPD Responces
+    const RESPONSE_OK             = 'OK';
+
+    private $_connection          = null;
+    protected $_errors            = array();
+    private $_current_error       = array();
+    protected $_commands          = array();
+    protected $_output            = array();
+    private $connection_params    = array();
+
+
+
+    /**
+     * Set connection params
+     *
+     * @param $host host to connect to, (default: localhost)
+     * @param $port port to connec through, (default: 6600)
+     * @param $password password to send, (default: null)
+     * @return void
+     */
+    function __construct($host = 'localhost', $port = 6600, $password = null)
+    {
+        $this->connection_params['host'] = $host;
+        $this->connection_params['port'] = $port;
+        $this->connection_params['password'] = $password;
+    }
+
+
+
+    /**
+     * Connect to MPD
+     *
+     * @return bool
+     */
+    public function connect()
+    {
+        if ($this->isConnected()) {
+            return true;
+        }
+        $connection = @fsockopen($this->connection_params['host'], $this->connection_params['port'], $errn, $errs, 4);
+        if ($connection) {
+            $this->_connection = $connection;
+            // Read from the source until its ready for commands
+            //$this->read();
+            while (!feof($this->_connection)) {
+                $line = fgets($this->_connection);
+                if (trim(substr($line, 0, 2)) == self::RESPONSE_OK) {
+                    break;
+                }
+            }
+            if (!is_null($this->connection_params['password'])) {
+                if (!$this->runCommand('password', $this->connection_params['password'])) {
+                    throw new PEAR_Exception('Password invalid.', self::ACK_PASSWORD);
+                }
+            }
+            return true;
+        }
+        throw new PEAR_Exception('Error connecting: '.$errn.' ; '.$errs, self::CONNECTION_FAILED);
+    }
+
+
+
+    /**
+     * Check connection status
+     *
+     * @return bool
+     */
+    public function isConnected()
+    {
+        if (!is_resource($this->_connection)) {
+            return false;
+        }
+        return true;
+    }
+
+
+
+    /**
+     * Disconnect from MPD
+     *
+     * @return bool
+     */
+    public function disconnect()
+    {
+       $this->runCommand('close');
+       fclose($this->_connection);
+       $this->_connection = null;
+       return true;
+    }
+
+
+
+    /**
+     * Write data to the socket
+     *
+     * @param $command string data to be sent
+     *
+     * @return bool
+     *
+     */
+    function write($data)
+    {
+        //Are we connected?
+        if (!$this->isConnected()) {
+            // Try to connect
+           $this->connect();
+        }
+        //Write the data
+        if (!fwrite($this->_connection, $data."\r\n")) {
+            throw new PEAR_Exception('Write failed', self::WRITE_FAILED);
+        }
+        $this->_commands[] = $data;
+        return true;
+    }
+
+
+
+    /**
+     * Read data from the socket
+     *
+     * @return array of raw output
+     *
+     */
+    function read()
+    {
+        //Are we connected?
+        if (!$this->isConnected()) {
+            throw new PEAR_Exception('Not connected', self::CONNECTION_NOT_OPENED);
+        }
+        //Loop through the connection, putting the data into $line
+        $output = array();
+        while (!feof($this->_connection)) {
+            $line = fgets($this->_connection);
+            if (preg_match('/^ACK \[(.*?)\@(.*?)\] \{(.*?)\} (.*?)$/', $line, $matches)) {
+                //If the output is an ACK error
+                //$this->runCommand('clearerror'); //Cleanup the error
+                $this->_errors[] = $matches;
+                $this->_current_error = array('ack' => $matches[1], 'func' => $matches[3], 'error' => $matches[4]);
+                throw new PEAR_Exception('Command Failed', self::ACK_COMMAND_FAILED);
+            } elseif (trim($line) == self::RESPONSE_OK) {
+                //The last line of output was hit, close the loop
+                break;
+            } else {
+                //Output from the server added to the return array
+                $output[] = $line;
+            }
+        }
+        return $output;
+    }
+
+
+
+    /**
+     * Get the current error data
+     *
+     * @return array of error data
+     */
+    public function getErrorData()
+    {
+        return $this->_current_error;
+    }
+
+
+    public function clearError() {
+         $this->runCommand('clearerror'); //Cleanup the error
+    }
+
+    /**
+     * Run command
+     *
+     * @param $command string a command to be executed through MPD
+     * @param $args mixed string for a single argument, array for multiple
+     * @param $parse mixed false to parse the output, int for parse style
+     *
+     * @return array of server output
+     */
+    public function runCommand($command, $args = array(), $parse = 0)
+    {
+        //Generate the command
+        if (is_array($args)) {
+            foreach($args as $arg) {
+                $command.= ' "'.str_replace('"', '\"', $arg) .'"';
+            }
+        } elseif (!is_null($args)) {
+            $command.= ' "'.str_replace('"', '\"', $args) .'"';
+        }
+        //Write and then capture the output
+       $this->write($command);
+       $output = $this->read();
+       
+        $this->_output[] = array($command, $output);
+        if ($output === array()) {
+            return true;
+        }
+        if ($parse !== false) {
+            return $this->parseOutput($output, $parse);
+        }
+        return $output;
+    }
+
+
+
+    /**
+     * Parse MPD output on a line-by-line basis
+     * creating output that is easy to work with
+     *
+     * @param $input array of input from MPD
+     * @param $style int style number,'0' for the "intelligent" sorting
+     *
+     * @return array
+     */
+    public function parseOutput($input, $style = 0)
+    {
+        if (!is_array($input)) {
+            $input = array($input);
+        }
+        $count = array('outputs' => 0, 'file' => -1, 'key' => 0);
+        $used_keys = array();
+        $output = array();
+        $prev = array('key' => null, 'value' => null);
+        $dirtoggle = false;
+        foreach($input as $line) {
+            if (is_array($line)) {
+                $this->_errors[] = 'Server output not expected: '.print_r($line, true);
+                continue;
+            } else {
+                $parts = explode(': ', $line, 2);
+                if (!isset($parts[0], $parts[1])) {
+                    $this->errors[] = 'Server output not expected: '.$line;
+                    continue;
+                }
+            }
+            $key = trim($parts[0]);
+            $value = trim($parts[1]);
+            if ($style == 0) {
+                switch ($key) {
+                    //The following has to do strictly
+                    //with files in the output
+                    case 'file':
+                    case 'Artist':
+                    case 'Album':
+                    case 'Title':
+                    case 'Track':
+                    case 'Name':
+                    case 'Genre':
+                    case 'Date':
+                    case 'Composer':
+                    case 'Performer':
+                    case 'Comment':
+                    case 'Disc':
+                    case 'Id':
+                    case 'Pos':
+                    case 'Time':
+                    case 'cpos':
+                        if ($key == 'file'||$key== 'cpos') {
+                            $count['file']++;
+                        }
+                        $output['file'][$count['file']][$key] = $value;
+                        break;
+
+                    //The next section is for a 'stats' call
+                    case 'artists':
+                    case 'albums':
+                    case 'songs':
+                    case 'uptime':
+                    case 'playtime':
+                    case 'db_playtime':
+                    case 'db_update':
+                        $output['stats'][$key] = $value;
+                        break;
+
+                    //Now for a status call:
+                    case 'volume':
+                    case 'repeat':
+                    case 'random':
+                    case 'playlistlength':
+                    case 'xfade':
+                    case 'state':
+                    case 'song':
+                    case 'songid':
+                    case 'time':
+                    case 'bitrate':
+                    case 'audio':
+                    case 'updating_db':
+                    case 'error':
+                        $output['status'][$key] = $value;
+                        break;
+
+                    //Outputs
+                    case 'outputid':
+                    case 'outputname':
+                    case 'outputenabled':
+                        if ($key == 'outputid') {
+                            $count['outputs']++;
+                        }
+                        $output['outputs'][$count['outputs']][$key] = $value;
+                        break;
+
+                    //The 'playlist' case works in 2 scenarios
+                    //1) in a file/directory listing
+                    //2) in a status call
+                    // This is to determine if it was in a status call
+                    // or in a directory call.
+                    case 'playlist':
+                        if ($prev['key'] == 'random') {
+                            $output['status'][$key] = $value;
+                        } else {
+                            $output[$key][] = $value;
+                        }
+                        break;
+
+                    //Now that we've covered most of the weird
+                    //options of output,
+                    //lets cover everything else!
+                    default:
+                        if (isset($used_keys[$key])) {
+                            $used_keys = array();
+                            $count['key']++;
+                        }
+                        $used_keys[$key] = true;
+                        //$output[$count['key']][$key] = $value;//This is rarely useful
+                        $output[$key][] = $value;
+                        break;
+                }
+            } elseif ($style == 1) {
+                $output[$key][] = $value;
+            }
+            if ($key == 'directory') {
+                $dirtoggle = true;
+            }
+            $prev['key'] = $key;
+            $prev['value'] = $value;
+        }
+        return $output;
+    }
+
+
+
+    /**
+     * A method to access errors
+     *
+     * @return array
+     */
+    public function getErrors()
+    {
+        return $this->_errors;
+    }
+
+
+
+    /**
+     * Used to discover commands that are not available
+     *
+     * @return array (null on no functions not being available)
+     */
+    public function getNotCommands()
+    {
+       $cmds = $this->runCommand('notcommands');
+        if (!isset($cmds['command'])) {
+            return array();
+        }
+        return $cmds['command'];
+    }
+
+
+
+    /**
+     * Used to discover which commands are available
+     *
+     * @return array (null on no functions being available
+     */
+    public function getCommands()
+    {
+       $cmds = $this->runCommand('commands');
+       if (!isset($cmds['command'])) {
+            return array();
+        }
+        return $cmds['command'];
+    }
+
+    public function hasCommand($name) {
+        $cmds = $this->getCommands();
+        foreach ($cmds as $cmd)
+            if($cmd==$name)
+                return true;
+        return false;
+    }
+
+
+
+    /**
+     * Ping the MPD server to keep the connection running
+     *
+     * @return bool
+     */
+    public function ping()
+    {
+       if ($this->runCommand('ping')) {
+           return true;
+       }
+        return false;
+    }
+
+
+
+    /**
+     * Get various statistics about the MPD server
+     *
+     * @return array
+     */
+    public function getStats()
+    {
+       $stats = $this->runCommand('stats');
+       if (!isset($stats['stats'])) {
+           return false;
+       }
+       return $stats['stats'];
+    }
+
+
+
+    /**
+     * Get the status of the MPD server
+     *
+     * @return array
+     */
+    public function getStatus()
+    {
+       $status = $this->runCommand('status');
+       if (!isset($status['status'])) {
+           return false;
+       }
+       return $status['status'];
+    }
+}
+?>
diff --git a/inc/Net/Net/MPD/Admin.php b/inc/Net/Net/MPD/Admin.php
new file mode 100644 (file)
index 0000000..5ca9c85
--- /dev/null
@@ -0,0 +1,89 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ *
+ *
+ * API for the administrative portion of Music Player Daemon commands
+ *
+ * Used for maintaining and controlling various administrative tasks
+ * of the MPD software.
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+class Net_MPD_Admin extends Net_MPD_Common
+{
+
+    /**
+    * List available audio outputs
+    *
+    * @return array or int on failure
+    */
+    public function getOutputs()
+    {
+       return $this->runCommand('outputs');
+    }
+
+    /**
+    * Disables an audio output
+    *
+    * @param $id int output Id to disable
+    * @return bool
+    */
+    public function disableOutput($id)
+    {
+      return $this->runCommand('disableoutput', $id);
+    }
+
+    /**
+    * Enables an audio output
+    *
+    * @param $id int Id to enable
+    * @return bool
+    */
+    public function enableOutput($id)
+    {
+      return $this->runCommand('enableoutput', $id);
+    }
+
+    /**
+    * Kills the MPD server in a safe way, saving state if possible
+    *
+    * @return bool
+    */
+    public function kill()
+    {
+       $r = $this->runCommand('kill');
+       if ($r) {
+           @$this->disconnect();
+       }
+       return true;
+    }
+
+    /**
+    * Updates the music database
+    *
+    * @param $path string path which to search for music, optional
+    * @return bool
+    */
+    public function updateDatabase($path = '')
+    {
+       $this->runCommand('update', $path);
+        return true;
+    }
+}
+?>
diff --git a/inc/Net/Net/MPD/Database.php b/inc/Net/Net/MPD/Database.php
new file mode 100644 (file)
index 0000000..78b1e64
--- /dev/null
@@ -0,0 +1,123 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+
+/**
+ * API for the database portion of Music Player Daemon commands
+ *
+ * Used for maintaining and working with the MPD database
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+class Net_MPD_Database extends Net_MPD_Common
+{
+    /**
+     * Case sensitive search for data in the database
+     *
+     * @param array $params         array('search_field' => 'search for')
+     * @param bool  $caseSensitive  True for case sensitivity, false for not [default false]
+     * @return array
+     */
+    public function find($params, $caseSensitive = false)
+    {
+        $prms = array();
+        foreach($params as $key => $value) {
+            $prms[] = $key;
+            $prms[] = $value;
+        }
+        $cmd = $caseSensitive ? 'find' : 'search';
+       
+       $out = $this->runCommand($cmd, $prms);
+       if (!isset($out['file'])) {
+           return array();
+       }
+       return $out['file'];
+    }
+
+
+
+    /**
+     * List all metadata of matches to the search
+     *
+     * @param string $metadata1 metadata to list
+     * @param string $metadata2 metadata field to search in, optional
+     * @param string $search    data to search for in search field,
+     *                          required if search field provided
+     * @return array
+     */
+    public function getMetadata($metadata1, $metadata2 = null, $search = null)
+    {
+        //Make sure that if metadata2 is set, search is as well
+        if (!is_null($metadata2)) {
+            if (is_null($search)) {
+                return false;
+            }
+        }
+       if (!is_null($metadata2)) {
+           $out = $this->runCommand('list', array($metadata1, $metadata2, $search), 1);
+       } else {
+           $out = $this->runCommand('list', $metadata1, 1);
+       }
+       return $out[$metadata1];
+    }
+
+
+
+    /**
+     * Lists all files and folders in the directory recursively
+     *
+     * @param $dir string directory to start in, optional
+     * @return array
+     */
+    public function getAll($dir = '')
+    {
+       return $this->runCommand('listall', $dir, 1);
+    }
+
+
+
+    /**
+     * Lists all files/folders recursivly, listing any related informaiton
+     *
+     * @param $dir string directory to start in, optional
+     * @return array
+     */
+    public function getAllInfo($dir = '')
+    {
+       return $this->runCommand('listallinfo', $dir);
+    }
+
+    /**
+     * Lists content of the directory
+     *
+     * @param $dir string directory to work in, optional
+     * @return array
+     */
+    public function getInfo($dir = '')
+    {
+       return $this->runCommand('lsinfo', $dir);
+    }
+}
+?>
diff --git a/inc/Net/Net/MPD/Playback.php b/inc/Net/Net/MPD/Playback.php
new file mode 100644 (file)
index 0000000..f6e5b35
--- /dev/null
@@ -0,0 +1,213 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * Music Player Daemon API
+ *
+ * PHP Version 5
+ *
+ * LICENSE: This source file is subject to version 3.01 of the PHP license
+ * that is available thorugh the world-wide-web at the following URI:
+ * http://www.php.net/license/3_01.txt. If you did not receive a copy of
+ * the PHP License and are unable to obtain it through the web, please
+ * send a note to license@php.net so we can mail you a copy immediately.
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+/**
+ * API for the playback portion of Music Player Daemon commands
+ *
+ * For controlling playback aspects of MPD
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @version   CVS: $ID:$
+ */
+class Net_MPD_Playback extends Net_MPD_Common
+{
+    /**
+     * Gets the current song and related information
+     *
+     * @return array of data
+     */
+    public function getCurrentSong()
+    {
+       $out = $this->runCommand('currentsong');
+       if (!isset($out['file'][0])) {
+           return false;
+       }
+       return $out['file'][0];
+    }
+
+
+
+    /**
+     * Set crossfade between songs
+     *
+     * @param $sec int, seconds to crossfade
+     * @return bool
+     */
+    public function setCrossfade($sec)
+    {
+       $this->runCommand('crossfade', $sec);
+       return true;
+    }
+
+
+
+    /**
+     * Continue to the next song
+     *
+     * @return bool
+     */
+    public function nextSong()
+    {
+       $this->runCommand('next');
+       return true;
+    }
+
+    /**
+     * Go back to the previous song
+     *
+     * @return bool
+     */
+    public function previousSong()
+    {
+       $this->runCommand('previous');
+       return true;
+    }
+
+
+
+    /**
+     * Pauses or plays the audio
+     *
+     * @return bool
+     */
+    public function pause()
+    {
+       $this->runCommand('pause');
+       return true;
+    }
+
+
+
+    /**
+     * Starts playback
+     *
+     * @param $song int, song position in playlist to start playing at
+     * @return bool
+     */
+    public function play($song = 0)
+    {
+       $this->runCommand('play', $song);
+       return true;
+    }
+
+    /**
+     * Starts playback by Id
+     *
+     * @param $song int, song Id
+     * @return bool
+     */
+    public function playId($song = 0)
+    {
+       $this->runCommand('playid', $song);
+       return true;
+    }
+
+
+
+    /**
+     * Sets 'random' mode on/off
+     *
+     * @param $on bool true or false, for random or not (respectively),
+     optional
+     * @return bool
+     */
+    public function random($on = false)
+    {
+       $this->runCommand('random', (int)$on);
+       return true;
+    }
+
+
+
+    /**
+     * Sets 'random' mode on/off
+     * @access public
+     * @param $on bool true or false, for repeat or not (respectively),
+     optional
+     * @return true
+     */
+    public function repeat($on = false)
+    {
+       $this->runCommand('repeat', (int)$on);
+       return true;
+    }
+
+
+
+    /**
+     * Seek a position in a song
+     *
+     * @param $song int song position in playlist
+     * @param $time int time in seconds to seek to
+     * @return bool
+     */
+    public function seek($song, $time)
+    {
+       $this->runCommand('seek', array($song, $time));
+       return true;
+    }
+
+
+
+    /**
+     * Seek a position in a song
+     *
+     * @param $song int song Id
+     * @param $time int time in seconds to seek to
+     * @return bool
+     */
+    public function seekId($song, $time)
+    {
+       $this->runCommand('seekid', array($song, $time));
+       return true;
+    }
+
+
+
+    /**
+     * Set volume
+     *
+     * @param $vol int volume
+     * @return true
+     */
+    public function setVolume($vol)
+    {
+       $this->runCommand('setvol', $vol);
+       return true;
+    }
+
+
+
+    /**
+     * Stop playback
+     *
+     * @return bool
+     */
+    public function stop()
+    {
+       $this->runCommand('stop');
+       return true;
+    }
+}
+?>
\ No newline at end of file
diff --git a/inc/Net/Net/MPD/Playlist.php b/inc/Net/Net/MPD/Playlist.php
new file mode 100644 (file)
index 0000000..6c67f22
--- /dev/null
@@ -0,0 +1,306 @@
+<?php
+/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
+/**
+ * MPD Interaction API
+ *
+ * Net_MPD deals with socket interaction with MPD to ease the
+ * use of MPD in web applications.
+ *
+ * PHP Version 5
+ *
+ * @package   Net_MPD
+ * @category  Networking
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   PHP License 3.01
+ * @version   CVS: $ID:$
+ */
+
+/**
+ * API for the playlist portion of Music Player Daemon commands
+ *
+ * Used for maintaining, creating, and utilizing playlists in MPD
+ *
+ * @category  Networking
+ * @package   Net_MPD
+ * @author    Graham Christensen <graham.christensen@itrebal.com>
+ * @copyright 2006 Graham Christensen
+ * @license   http://www.php.net/license/3_01.txt
+ * @vers
+ */
+class Net_MPD_Playlist extends Net_MPD_Common
+{
+    /**
+     * List playlists in specified directory
+     *
+     * @param $dir string directory path, optional
+     * @return bool true on success int on failure
+     */
+    public function getPlaylists($dir = '')
+    {
+       $out = $this->runCommand('lsinfo', $dir);
+       return $out['playlist'];
+    }
+
+    /**
+     * Search for data in the playlist
+     *
+     * @param array $params         array('search_field' => 'search for')
+     * @param bool  $caseSensitive  True for case sensitivity, false for not [default false]
+     * @return array
+     */
+    public function find($params, $caseSensitive = false)
+    {
+        $prms = array();
+        foreach($params as $key => $value) {
+            $prms[] = $key;
+            $prms[] = $value;
+        }
+        $cmd = $caseSensitive ? 'playlistfind' : 'playlistsearch';
+       
+        $out = $this->runCommand($cmd, $prms);
+        if (!isset($out['file'])) {
+            return array();
+        }
+        return $out['file'];
+    }
+
+    /* Tests whether playlistfind is avilable. If playlistfind 
+     * is available playlistsearch is as well
+     *
+     * @return boolean
+     */
+    public function hasFind() {
+        return $this->hasCommand("playlistfind");
+    }
+
+    /**
+     * Add file to playlist
+     *
+     * @param $file string filename
+     * @return bool
+     */
+    public function addSong($file)
+    {
+       $this->runCommand('add', $file);
+       return true;
+    }
+
+
+
+    /**
+     * Clear the playlist
+     *
+     * @return bool
+     */
+    public function clear()
+    {
+       $this->runCommand('clear');
+       return true;
+    }
+
+
+
+    /**
+     * Gets the current song and related information
+     *
+     * @return array of data
+     */
+    public function getCurrentSong()
+    {
+       $out = $this->runCommand('currentsong');
+       if (!isset($out['file'][0])) {
+           return false;
+       }
+       return $out['file'][0];
+    }
+
+
+
+    /**
+     * Delete song from playlist
+     *
+     * @param $song int song position in playlist
+     * @return bool
+     */
+    public function deleteSong($song)
+    {
+       $this->runCommand('delete', $song);
+       return true;
+    }
+
+
+
+    /**
+     * Delete song from playlist by song Id
+     *
+     * @param $id int song Id
+     * @return bool
+     */
+    public function deleteSongId($id)
+    {
+       $this->runCommand('deleteid', $id);
+       return true;
+    }
+
+
+
+    /**
+     * Loads a playlist into the current playlist
+     *
+     * @param $playlist string playlist name
+     * @return bool
+     */
+    public function loadPlaylist($playlist)
+    {
+       $this->runCommand('load', $playlist);
+       return true;
+    }
+
+
+
+    /**
+     * Move song in the playlist
+     *
+     * @param $from int song position in the playlist
+     * @param $to int song position to move it to
+     * @return bool
+     */
+    public function moveSong($from, $to)
+    {
+       $this->runCommand('move', array($from, $to));
+       return true;
+    }
+
+
+
+    /**
+     * Move song in the playlist by Id
+     *
+     * @param $from int song Id
+     * @param $to int song Id to move it to
+     * @return bool
+     */
+    public function moveSongId($fromId, $toId)
+    {
+       $this->runCommand('moveid', array($fromId, $toId));
+       return true;
+    }
+
+
+
+    /**
+     * Displays metadata for songs in the playlist by position Id
+     *
+     * @param $song int song position, optional
+     * @return array of song metadata
+     */
+    public function getPlaylistInfo($song = null)
+    {
+       $out = $this->runCommand('playlistinfo', $song, 0);
+       return isset($out['file']) ? $out['file'] : array();
+    }
+
+
+
+    /**
+     * Displays metadata for songs in the playlist
+     *
+     * @param $song int song Id, optional
+     * @return array of song metadata
+     */
+    public function getPlaylistInfoId($song = null)
+    {
+       return $this->runCommand('playlistid', $song);
+    }
+
+
+
+    /**
+     * Get playlist changes
+     *
+     * @param $version int playlist version
+     * @param $limit boolean true to limit return
+     *               to only position and id
+     *
+     * @return array of changes
+     */
+    public function getChanges($version, $limit = false)
+    {
+        $cmd = $limit ? 'plchangesposid' : 'plchanges';
+       
+       return $this->runCommand($cmd, $version);
+    }
+
+
+
+    /**
+     * Delete a playlist
+     *
+     * @param $playlist string playlist name
+     * @return true
+     */
+    public function deletePlaylist($playlist)
+    {
+       $this->runCommand('rm', $playlist);
+       return true;
+    }
+
+
+
+    /**
+     * Save the playlist
+     *
+     * @param $playlist string playlist name
+     * @return bool
+     */
+    public function savePlaylist($playlist)
+    {
+       $this->runCommand('save', $playlist);
+       return true;
+    }
+
+
+
+    /**
+     * Shuffle the playlist
+     *
+     * @return true
+     */
+    public function shuffle()
+    {
+       $this->runCommand('shuffle');
+       return true;
+    }
+
+
+
+    /**
+     * Swap song by position in the playlist
+     *
+     * @param $song1 int song position from
+     * @param $song2 int song position to
+     * @return bool
+     */
+    public function swapSong($song1, $song2)
+    {
+       $this->runCommand('swap', array($song1, $song2));
+       return true;
+    }
+
+
+
+    /**
+     * Swaps a song with another song, by Id
+     *
+     * @param $song1 int Id of the first song
+     * @param $song2 int Id of the second song
+     * @return true
+     */
+    public function swapSongId($songId1, $songId2)
+    {
+       $this->runCommand('swapid', array($songId1, $songId2));
+       return true;
+    }
+}
+?>
diff --git a/inc/Net/package.xml b/inc/Net/package.xml
new file mode 100644 (file)
index 0000000..e5ea912
--- /dev/null
@@ -0,0 +1,135 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<!DOCTYPE package SYSTEM "http://pear.php.net/dtd/package-1.0">
+<package version="1.0" packagerversion="1.4.11">
+ <name>Net_MPD</name>
+ <summary>Music Player Daemon interaction API</summary>
+ <description>Rrovides wrappers to easily use the MPD socket commands.
+ </description>
+ <maintainers>
+  <maintainer>
+   <user>itrebal</user>
+   <name>Graham Christensen</name>
+   <email>graham.christensen@itrebal.com</email>
+   <role>lead</role>
+  </maintainer>
+  </maintainers>
+ <release>
+  <version>1.0.0</version>
+  <date>2007-01-18</date>
+  <license>PHP License 3.01</license>
+  <state>stable</state>
+  <notes>Removed unecissary try/catch blocks. Moving to stable.
+  </notes>
+  <deps>
+   <dep type="php" rel="ge" version="5.1.4"/>
+  </deps>
+  <provides type="class" name="Net_MPD" />
+  <provides type="function" name="Net_MPD::factory" />
+  <provides type="class" name="Net_MPD_Admin" extends="Net_MPD_Common" />
+  <provides type="function" name="Net_MPD_Admin::getOutputs" />
+  <provides type="function" name="Net_MPD_Admin::disableOutput" />
+  <provides type="function" name="Net_MPD_Admin::enableOutput" />
+  <provides type="function" name="Net_MPD_Admin::kill" />
+  <provides type="function" name="Net_MPD_Admin::updateDatabase" />
+  <provides type="class" name="Net_MPD_Common" />
+  <provides type="function" name="Net_MPD_Common::connect" />
+  <provides type="function" name="Net_MPD_Common::isConnected" />
+  <provides type="function" name="Net_MPD_Common::disconnect" />
+  <provides type="function" name="Net_MPD_Common::write" />
+  <provides type="function" name="Net_MPD_Common::read" />
+  <provides type="function" name="Net_MPD_Common::getErrorData" />
+  <provides type="function" name="Net_MPD_Common::runCommand" />
+  <provides type="function" name="Net_MPD_Common::parseOutput" />
+  <provides type="function" name="Net_MPD_Common::getErrors" />
+  <provides type="function" name="Net_MPD_Common::getNotCommands" />
+  <provides type="function" name="Net_MPD_Common::getCommands" />
+  <provides type="function" name="Net_MPD_Common::ping" />
+  <provides type="function" name="Net_MPD_Common::getStats" />
+  <provides type="function" name="Net_MPD_Common::getStatus" />
+  <provides type="class" name="Net_MPD_Database" extends="Net_MPD_Common" />
+  <provides type="function" name="Net_MPD_Database::find" />
+  <provides type="function" name="Net_MPD_Database::getMetadata" />
+  <provides type="function" name="Net_MPD_Database::getAll" />
+  <provides type="function" name="Net_MPD_Database::getAllInfo" />
+  <provides type="function" name="Net_MPD_Database::getInfo" />
+  <provides type="class" name="Net_MPD_Playback" extends="Net_MPD_Common" />
+  <provides type="function" name="Net_MPD_Playback::getCurrentSong" />
+  <provides type="function" name="Net_MPD_Playback::setCrossfade" />
+  <provides type="function" name="Net_MPD_Playback::nextSong" />
+  <provides type="function" name="Net_MPD_Playback::previousSong" />
+  <provides type="function" name="Net_MPD_Playback::pause" />
+  <provides type="function" name="Net_MPD_Playback::play" />
+  <provides type="function" name="Net_MPD_Playback::playId" />
+  <provides type="function" name="Net_MPD_Playback::random" />
+  <provides type="function" name="Net_MPD_Playback::repeat" />
+  <provides type="function" name="Net_MPD_Playback::seek" />
+  <provides type="function" name="Net_MPD_Playback::seekId" />
+  <provides type="function" name="Net_MPD_Playback::setVolume" />
+  <provides type="function" name="Net_MPD_Playback::stop" />
+  <provides type="class" name="Net_MPD_Playlist" extends="Net_MPD_Common" />
+  <provides type="function" name="Net_MPD_Playlist::getPlaylists" />
+  <provides type="function" name="Net_MPD_Playlist::addSong" />
+  <provides type="function" name="Net_MPD_Playlist::clear" />
+  <provides type="function" name="Net_MPD_Playlist::getCurrentSong" />
+  <provides type="function" name="Net_MPD_Playlist::deleteSong" />
+  <provides type="function" name="Net_MPD_Playlist::deleteSongId" />
+  <provides type="function" name="Net_MPD_Playlist::loadPlaylist" />
+  <provides type="function" name="Net_MPD_Playlist::moveSong" />
+  <provides type="function" name="Net_MPD_Playlist::moveSongId" />
+  <provides type="function" name="Net_MPD_Playlist::getPlaylistInfo" />
+  <provides type="function" name="Net_MPD_Playlist::getPlaylistInfoId" />
+  <provides type="function" name="Net_MPD_Playlist::getChanges" />
+  <provides type="function" name="Net_MPD_Playlist::deletePlaylist" />
+  <provides type="function" name="Net_MPD_Playlist::savePlaylist" />
+  <provides type="function" name="Net_MPD_Playlist::shuffle" />
+  <provides type="function" name="Net_MPD_Playlist::swapSong" />
+  <provides type="function" name="Net_MPD_Playlist::swapSongId" />
+  <filelist>
+   <file role="php" baseinstalldir="/" md5sum="f4adbf28dd06b747929ddd7345040571" name="Net/MPD.php"/>
+   <file role="php" baseinstalldir="/" md5sum="b2a9a9768c50a610d4ba7055dfef0e46" name="Net/MPD/Admin.php"/>
+   <file role="php" baseinstalldir="/" md5sum="a2301a1e04df755d70965ffe5265677f" name="Net/MPD/Common.php"/>
+   <file role="php" baseinstalldir="/" md5sum="b76d6fbf71fcede04d8816e1354a8e42" name="Net/MPD/Database.php"/>
+   <file role="php" baseinstalldir="/" md5sum="8a8a565029f2c21fc0683cbe3139ec46" name="Net/MPD/Playback.php"/>
+   <file role="php" baseinstalldir="/" md5sum="18d4bca6d89d276769ecee5186be4235" name="Net/MPD/Playlist.php"/>
+   <file role="doc" baseinstalldir="/" md5sum="3d2f2dd2aea4f3abebc9c80f11b4a097" name="Net/MPD/docs/listArtists.php"/>
+   <file role="doc" baseinstalldir="/" md5sum="a69ec1ca3c692b53e9d643777d6ef0ee" name="Net/MPD/docs/listCurrentPlaylist.php"/>
+   <file role="doc" baseinstalldir="/" md5sum="5c7b6ab616a1fcfbb98a000d9bfb57c6" name="Net/MPD/docs/listSongs.php"/>
+  </filelist>
+ </release>
+ <changelog>
+   <release>
+    <version>0.1.0dev1</version>
+    <date>2006-11-07</date>
+    <state>devel</state>
+    <notes>Initial release.
+    </notes>
+   </release>
+   <release>
+    <version>0.2.0dev</version>
+    <date>2006-11-12</date>
+    <state>devel</state>
+    <notes>- Copied `getCurrentSong&apos; from Net_MPD_Playlist to Net_MPD_Playback.
+- Changed MPD.php to provied an object uniting the sub-objects. $mpd-&gt;Playlist-&gt;addSong() for example. Left the factory for other uses.
+- Changed MPD/Common.php to auto-connect when a command is run. This is to prevent several connections being opend without need, if the Net_MPD class is used to group them all. Also simplifies the connection process.
+- Moved the connection parameters back into the __construct method of the Net_MPD_Common class.
+- Bugfix: defined $output before adding to it as an array, in Net_MPD_Common-&gt;read() function.
+    </notes>
+   </release>
+   <release>
+    <version>0.3.0dev</version>
+    <date>2006-12-10</date>
+    <state>devel</state>
+    <notes>- Fixed bug #9392; Error codes can now be accessed using Exception::getCode()
+- Fixed bug #9334; Now installs into PEAR/Net/
+- Fixed bug #9341; Now permits $e-&gt;getCode();
+    </notes>
+   </release>
+   <release>
+    <version>1.0.0</version>
+    <date>2007-01-18</date>
+    <state>stable</state>
+    <notes>Removed unecissary try/catch blocks. Moving to stable.
+    </notes>
+   </release>
+ </changelog>
+</package>
diff --git a/inc/base.php b/inc/base.php
new file mode 100644 (file)
index 0000000..fbc1072
--- /dev/null
@@ -0,0 +1,190 @@
+<?php
+if(function_exists("mb_internal_encoding"))
+       mb_internal_encoding("UTF-8");
+define('HASH_PASS', "********");
+define('SALT_LENGTH', 15);
+
+session_start();
+
+/* make sure . is first in include path */
+set_include_path("." . PATH_SEPARATOR . get_include_path());
+require_once('Net/MPD.php');
+
+$release_version = "0.5.5";
+$release_date = "18-01-2008";
+
+$config_dir = "../config";
+
+/* should be metadata_dir, no time to change now TODO */
+$cover_dir = "$config_dir/metadata/"; 
+$metadata_dir = $cover_dir;
+
+
+$theme_dir = "../theme/";
+
+$config = @simplexml_load_file("../config/config.xml");
+$error_msg = false;
+
+/* playlist fields */
+$pl_fields = array("Title", "Album", "Artist", "Track", "Name", "Genre", "Date", "Composer", "Performer", "Comment", "Disc");
+
+if(!$config&&!isset($_GET['new_config'])) {
+       header("Location: config.php?new_config=true") or die("Unable to redirect, go here <a href='config.php?new_config=true'>config</a>");
+       exit(); 
+}
+
+$language = get_config("lang", "en");
+
+
+$selected_theme = get_config("theme", "default");
+if(!is_theme_dir_ok($theme_dir . $selected_theme)) 
+       $selected_theme = "default";
+
+$lpass = get_config('login_pass');
+
+if(!is_null($lpass)&&$lpass!="") {
+       if(!isset($_SESSION['logged_in'])||!$_SESSION['logged_in']) {
+               if(!isset($no_require_login)) {
+                       header("Location: login.php");
+                       echo "Wrong password";
+                       exit();
+               }
+       }
+}
+
+function get_config($name, $default = null) {
+       global $config;
+       if(isset($config->$name)) {
+               if(trim($config->$name)=="")
+                       return $default;
+               return ((string)$config->$name);
+       }
+       else {
+               return $default;
+       }
+}
+
+function get_selected_plfields() {
+       global $config, $pl_fields;
+       $plentry = false;
+       if(isset($config->plentry)) 
+               $plentry = $config->plentry;
+       $ret = array();
+       $i = 0;
+       foreach($pl_fields as $field) {
+               if($plentry) {
+                       $ret[] = ((string)$plentry->$field)!="";
+               }
+               else {
+                       $ret[] = $i++<3;
+               }
+       }
+       return $ret;
+}
+
+function set_config($name, $value) {
+
+}
+/* if a key is found to be numeric it will be replaced by numb_replace 
+*/
+function array_to_xml($arr, $xml = null, $numb_replace = "elem") {
+       if(is_null($xml)) {
+               $xml = simplexml_load_string("<?xml version='1.0' ?><root/>");
+       }
+       foreach($arr as $key => $value) {
+               if(is_numeric($key)) 
+                       $key = $numb_replace;
+               if(is_array($value)) {
+                       $tmp = $xml->addChild($key);
+                       array_to_xml($value, $tmp, $numb_replace);
+               }
+               else {
+                       $xml->addChild($key, htmlspecialchars($value));
+               }
+       }
+       return $xml;
+}
+
+function get_mpd($type) {
+       try {
+               $port = 6600;
+               if(is_numeric(get_config("mpd_port")))
+                       $port = (int) get_config("mpd_port");
+               $ret = Net_MPD::factory($type, get_config('mpd_host'), intval($port), get_config("mpd_pass"));
+               $ret->connect();
+               return $ret;
+       }
+       catch(PEAR_Exception $e) {
+               return false;
+       }
+}
+function get_playlist() {
+       return get_mpd("Playlist");
+}
+function get_playback() {
+       return get_mpd("Playback");
+}
+function get_database() {
+       return get_mpd("Database");
+}
+function get_admin() {
+       return get_mpd("Admin");
+}
+
+/* mimic behaviour of java System.currentTimeMillis() */
+// ex: 1172151695935.8
+function current_time_millis() {
+       return microtime(true)*1000;
+}
+
+// returns array with available themes
+function get_available_themes() {
+       global $theme_dir;
+       $dirs = scandir($theme_dir);
+       $themes = array();
+       foreach($dirs as $dir) {
+               if($dir=="."||$dir=="..")
+                       continue;
+
+               if(is_theme_dir_ok($theme_dir . $dir)) {
+                       $themes[] = $dir;
+               }
+       }
+       return $themes;
+}
+
+function is_theme_dir_ok($tdir) {
+       return is_dir($tdir) && file_exists($tdir . "/theme.css")  && file_exists($tdir . "/theme.js");
+}
+
+function generate_hash($val, $salt = false) {
+       if(function_exists("hash")) {
+               if($salt===false)
+                       $salt = substr(md5(uniqid(rand(), true)), 0, SALT_LENGTH);
+               
+               $p = hash("sha256", $val . $salt);
+               return "sha:" . $p . $salt;
+       }
+       else return $val;
+}
+function check_hash($proper, $check) {
+       $len = strlen($proper);
+       $nhash = generate_hash($check, substr($proper, $len-SALT_LENGTH));
+       if($proper==$nhash) {
+               return true;
+       }
+       return false;
+}
+
+/* someone find me a php equiv */
+function str_byte_count($data) {
+       if(function_exists("mb_strlen")) {
+               /* count bytes, not characters if utf-8, 
+                * I imagine this works, but hard to actually test  */
+               return mb_strlen($data, "ascii");
+       }
+       else {
+               return strlen($data);
+       }
+}
+?>
diff --git a/inc/function_test.php b/inc/function_test.php
new file mode 100644 (file)
index 0000000..5dc63ee
--- /dev/null
@@ -0,0 +1,35 @@
+<?php
+/* 
+    Pitchfork Music Player Daemon Client
+    Copyright (C) 2007  Roger Bystrøm
+
+    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; version 2 of the License.
+
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+
+       /* test for required stuff */
+       $fn_prob = false;
+
+       foreach(array("simplexml_load_string", "simplexml_load_file") as $fn) {
+               if(!function_exists($fn)) {
+                       $fn_prob = $fn_prob?"$fn_prob $fn":"$fn";
+               }
+       }
+
+       if($fn_prob) {
+               echo "You are missing function(s): $fn_prob. Cowardly bailing out..\n";
+               echo "This means that your php installation is either compiled without simplexml support or you have an old version of PHP. Version 5.1.3 or later is required.\n";
+               exit();
+       }
+
+?>
diff --git a/index.php b/index.php
new file mode 100644 (file)
index 0000000..2f8247e
--- /dev/null
+++ b/index.php
@@ -0,0 +1,21 @@
+<?php
+/* 
+    Pitchfork Music Player Daemon Client
+    Copyright (C) 2007  Roger Bystrøm
+
+    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; version 2 of the License.
+
+    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.,
+    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+*/
+       header("Location: player/index.php");
+       exit(); 
+?>
diff --git a/jorbis/build.xml b/jorbis/build.xml
new file mode 100644 (file)
index 0000000..e893b20
--- /dev/null
@@ -0,0 +1,44 @@
+<project name="jorbis-pitchfork" basedir="." default="all">
+
+       <!--
+               run "ant jar" to create the needed jar
+               To sign it you'll need a selfsigned certificate, see seperate 
+               comment about that
+       -->
+       <property name="source" value="src"/>
+       <property name="build" value="build"/>
+       <property name="classes" value="${build}/classes"/>
+       <property name="jarfile" value="jorbis-pitchfork.jar"/>
+
+       <target name="clean">
+           <delete dir="${build}"/>
+       </target>
+
+       <target name="compile">
+               <mkdir dir="${classes}"/>
+               <javac srcdir="${source}" destdir="${classes}"/>
+       </target>
+
+       <target name="jar" depends="compile">
+
+               <jar destfile="jorbis-pitchfork.jar" basedir="${classes}">
+                       <manifest>
+                               <attribute name="Main-Class" value="JorbisPlayer"/>
+                       </manifest>
+               </jar>
+       </target>
+
+
+       <!-- To create your own certificate: 
+               keytool -genkey -alias pitchfork -validity 3650
+               keytool -selfcert -alias pitchfork -validity 3650
+       -->
+       <target name="signjar" depends="jar">
+               <signjar storepass="secret" jar="${jarfile}" alias="pitchfork">
+               </signjar>
+       </target>
+
+       <target name="all" depends="clean,compile,jar,signjar">
+       </target>
+
+</project>
diff --git a/jorbis/jorbis-pitchfork.jar b/jorbis/jorbis-pitchfork.jar
new file mode 100644 (file)
index 0000000..8117b26
Binary files /dev/null and b/jorbis/jorbis-pitchfork.jar differ
diff --git a/jorbis/src/JOrbisPlayer.java b/jorbis/src/JOrbisPlayer.java
new file mode 100644 (file)
index 0000000..1f8d062
--- /dev/null
@@ -0,0 +1,714 @@
+/* -*-mode:java; c-basic-offset:2; indent-tabs-mode:nil -*- */
+/* JOrbisPlayer -- pure Java Ogg Vorbis player
+ *
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec and
+ * JOrbisPlayer depends on JOrbis.
+ *
+ * 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., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+/*
+ * Modified 2007 by Roger Bystrøm for pitchfork <pitchfork@remiss.org>
+ */
+
+import java.util.*;
+import java.io.BufferedReader;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.net.*;
+
+import java.awt.*;
+import java.awt.event.*;
+import java.applet.*;
+import javax.swing.*;
+
+import com.jcraft.jorbis.*;
+import com.jcraft.jogg.*;
+
+import javax.sound.sampled.*;
+
+public class JOrbisPlayer extends JApplet implements ActionListener {
+
+       Thread player=null;
+       InputStream bitStream=null;
+       URLConnection urlc = null;
+
+       public static final String PLAY = "Play", STOP = "Stop";
+       
+       static AppletContext acontext=null;
+
+       static final int BUFSIZE=4096*2;
+       static int convsize=BUFSIZE*2;
+       static byte[] convbuffer=new byte[convsize]; 
+
+       PlayWatch playThread;
+       
+       private int RETRY=3;
+       int retry=RETRY;
+
+       SyncState oy;
+       StreamState os;
+       Page og;
+       Packet op;
+       Info vi;
+       Comment vc;
+       DspState vd;
+       Block vb;
+
+       byte[] buffer=null;
+       int bytes=0;
+
+       int format;
+       int rate=0;
+       int channels=0;
+       int left_vol_scale=100;
+       int right_vol_scale=100;
+       SourceDataLine outputLine=null;
+       String current_source=null;
+
+       int frameSizeInBytes;
+       int bufferLengthInBytes;
+
+       boolean playonstartup=false;
+
+       public Color bgColor = Color.lightGray;
+
+       public void init(){
+
+               playThread = new PlayWatch();
+               playThread.start();
+
+               acontext=getAppletContext();
+
+               loadPlaylist();
+
+               if(playlist.size()>0){
+                       String s=getParameter("jorbis.player.playonstartup");
+                       if(s!=null && s.equals("yes"))
+                               playonstartup=true; 
+               }
+
+               String c = getParameter("jorbis.player.bgcolor");
+
+               try {
+                       bgColor = new Color(Integer.parseInt(c));
+               } catch (Exception e) {}
+
+               System.out.println("Bg-color: specified: " +c + ", using: " +  bgColor.toString());
+
+               initUI();
+
+               getContentPane().setLayout(new BorderLayout());
+               getContentPane().add(panel);
+               setBackground(bgColor);
+               repaint();
+
+       }
+
+       public void start(){
+               super.start();
+               if(playonstartup){
+                       play_sound(); 
+               }
+       }    
+
+       void init_jorbis(){
+               oy=new SyncState();
+               os=new StreamState();
+               og=new Page();
+               op=new Packet();
+
+               vi=new Info();
+               vc=new Comment();
+               vd=new DspState();
+               vb=new Block(vd);
+
+               buffer=null;
+               bytes=0;
+
+               oy.init();
+       }
+
+       public void closeOutputLine() {
+               if(outputLine!=null){
+                       //outputLine.drain();
+                       outputLine.stop();
+                       outputLine.flush();
+                       outputLine.close();
+                       outputLine = null;
+               }
+       }
+
+       public void closeBitStream() {
+               if(bitStream!=null) {
+                       try {
+                               bitStream.close();
+                       } catch(Exception ee) {}
+               }
+       }
+
+       SourceDataLine getOutputLine(int channels, int rate) throws Exception {
+               if(outputLine==null || this.rate!=rate || this.channels!=channels){
+                       closeOutputLine();
+                       init_audio(channels, rate);
+                       outputLine.start();
+               }
+               return outputLine;
+       }
+
+       void init_audio(int channels, int rate) throws Exception {
+               try {
+
+                       AudioFormat audioFormat = 
+                               new AudioFormat((float)rate, 
+                                               16,
+                                               channels,
+                                               true,  // PCM_Signed
+                                               false  // littleEndian
+                               );
+                       DataLine.Info info = 
+                               new DataLine.Info(SourceDataLine.class,
+                                               audioFormat, 
+                                               AudioSystem.NOT_SPECIFIED);
+                       if (!AudioSystem.isLineSupported(info)) {
+                               //System.out.println("Line " + info + " not supported.");
+                               return;
+                       }
+
+                       try{
+                               outputLine = (SourceDataLine) AudioSystem.getLine(info);
+                               //outputLine.addLineListener(this);
+                               outputLine.open(audioFormat);
+                       } catch (LineUnavailableException ex) { 
+                               System.out.println("Unable to open the sourceDataLine: " + ex);
+                               if(acontext != null)
+                                       acontext.showStatus("Streaming applet: unable to open output device");
+                               throw ex;
+                       } 
+
+                       frameSizeInBytes = audioFormat.getFrameSize();
+                       int bufferLengthInFrames = outputLine.getBufferSize()/frameSizeInBytes/2;
+                       bufferLengthInBytes = bufferLengthInFrames * frameSizeInBytes;
+
+                       this.rate=rate;
+                       this.channels=channels;
+               }
+               catch(Exception ee){
+                       System.out.println(ee);
+                       closeOutputLine();
+                       throw ee;
+               }
+       }
+       
+       private void play_stream(Thread me) {
+
+               boolean chained=false;
+
+               init_jorbis();
+
+               retry=RETRY;
+
+               loop:
+                       while(true){
+                               int eos=0;
+
+                               int index=oy.buffer(BUFSIZE);
+                               buffer=oy.data;
+                               try{ 
+                                       bytes=bitStream.read(buffer, index, BUFSIZE); 
+                               }
+                               catch(Exception e){
+                                       System.err.println(e);
+                                       return;
+                               }
+                               oy.wrote(bytes);
+
+                               if(chained){
+                                       chained=false;
+                               }
+                               else {
+                                       if(oy.pageout(og)!=1){
+                                               if(bytes<BUFSIZE)break;
+                                               System.err.println("Input does not appear to be an Ogg bitstream.");
+                                               return;
+                                       }
+                               }
+                               os.init(og.serialno());
+                               os.reset();
+
+                               vi.init();
+                               vc.init();
+
+                               if(os.pagein(og)<0){ 
+                                       // error; stream version mismatch perhaps
+                                       System.err.println("Error reading first page of Ogg bitstream data.");
+                                       return;
+                               }
+
+                               retry=RETRY;
+
+                               if(os.packetout(op)!=1){ 
+                                       // no page? must not be vorbis
+                                       System.err.println("Error reading initial header packet.");
+                                       break;
+                               }
+
+                               if(vi.synthesis_headerin(vc, op)<0){ 
+                                       // error case; not a vorbis header
+                                       System.err.println("This Ogg bitstream does not contain Vorbis audio data.");
+                                       return;
+                               }
+
+                               int i=0;
+
+                               while(i<2){
+                                       while(i<2){
+                                               int result=oy.pageout(og);
+                                               if(result==0) break; // Need more data
+                                               if(result==1){
+                                                       os.pagein(og);
+                                                       while(i<2){
+                                                               result=os.packetout(op);
+                                                               if(result==0)break;
+                                                               if(result==-1){
+                                                                       System.err.println("Corrupt secondary header.  Exiting.");
+                                                                       //return;
+                                                                       break loop;
+                                                               }
+                                                               vi.synthesis_headerin(vc, op);
+                                                               i++;
+                                                       }
+                                               }
+                                       }
+
+                                       index=oy.buffer(BUFSIZE);
+                                       buffer=oy.data; 
+                                       try{ bytes=bitStream.read(buffer, index, BUFSIZE); }
+                                       catch(Exception e){
+                                               System.err.println(e);
+                                               return;
+                                       }
+                                       if(bytes==0 && i<2){
+                                               System.err.println("End of file before finding all Vorbis headers!");
+                                               return;
+                                       }
+                                       oy.wrote(bytes);
+                               }
+
+                               {
+                                       byte[][] ptr=vc.user_comments;
+                                       StringBuffer sb=null;
+                                       if(acontext!=null) sb=new StringBuffer();
+                                       String artist = null, title = null, album = null, tmp;
+                                       for(int j=0; j<ptr.length;j++){
+                                               if(ptr[j]==null) break;
+                                               tmp = new String(ptr[j], 0, ptr[j].length-1);
+                                               System.err.println("Comment: "+tmp);
+                                               if(sb!=null) {
+                                                       if(tmp.startsWith("ARTIST")) 
+                                                               artist = new String(ptr[j], 7, ptr[j].length-8);
+                                                       else if(tmp.startsWith("TITLE"))
+                                                               title = new String(ptr[j], 6, ptr[j].length-7);
+                                                       else if(tmp.startsWith("ALBUM"))
+                                                               album = new String(ptr[j], 6, ptr[j].length-7);
+                                                       else
+                                                               sb.append(" "+tmp);
+                                               }
+                                       } 
+                                       System.err.println("Bitstream is "+vi.channels+" channel, "+vi.rate+"Hz");
+                                       System.err.println("Encoded by: "+new String(vc.vendor, 0, vc.vendor.length-1)+"\n");
+                                       if(acontext!=null) {
+                                               StringBuffer stat = new StringBuffer();
+                                               if(title!=null) {
+                                                       stat.append(title);
+                                                       if(album!=null) {
+                                                               stat.append(" on ");
+                                                               stat.append(album);
+                                                       }
+
+                                               }
+                                               else if(album!=null){ // hmm
+                                                       stat.append("Album ");
+                                                       stat.append(album);
+                                               }
+                                               
+                                               if(artist!=null) {
+                                                       stat.append(" by ");
+                                                       stat.append(artist);
+                                               }
+                                               
+                                               if(sb.length()>0)
+                                                       stat.append(" " +sb);
+                                               acontext.showStatus(stat.toString());
+                                       }
+                               }
+
+                               convsize=BUFSIZE/vi.channels;
+
+                               vd.synthesis_init(vi);
+                               vb.init(vd);
+
+                               float[][][] _pcmf=new float[1][][];
+                               int[] _index=new int[vi.channels];
+
+                               try {
+                                       getOutputLine(vi.channels, vi.rate);
+                               } catch(Exception e) {
+                                       stop_sound();
+                                       return;
+                               }
+
+                               while(eos==0){
+                                       while(eos==0){
+
+                                               if(player!=me){
+                                                       System.err.println("player!=me bye.");
+                                                       closeBitStream();
+                                                       closeOutputLine();
+                                                       if(acontext!=null)
+                                                               acontext.showStatus("");
+                                                       return;
+                                               }
+
+                                               int result=oy.pageout(og);
+                                               if(result==0)break; // need more data
+                                               if(result==-1){ // missing or corrupt data at this page position
+                                                       //System.err.println("Corrupt or missing data in bitstream; continuing...");
+                                               }
+                                               else{
+                                                       os.pagein(og);
+
+                                                       if(og.granulepos()==0){  //
+                                                               chained=true;          //
+                                                               eos=1;                 // 
+                                                               break;                 //
+                                                       }                        //
+
+                                                       while(true){
+                                                               result=os.packetout(op);
+                                                               if(result==0)break; // need more data
+                                                               if(result==-1){ 
+                                                                       // missing or corrupt data at this page position
+                                                               }
+                                                               else{
+                                                                       // we have a packet.  Decode it
+                                                                       int samples;
+                                                                       if(vb.synthesis(op)==0){ // test for success!
+                                                                               vd.synthesis_blockin(vb);
+                                                                       }
+                                                                       while((samples=vd.synthesis_pcmout(_pcmf, _index))>0){
+                                                                               float[][] pcmf=_pcmf[0];
+                                                                               int bout=(samples<convsize?samples:convsize);
+
+                                                                               // convert doubles to 16 bit signed ints (host order) and
+                                                                               // interleave
+                                                                               for(i=0;i<vi.channels;i++){
+                                                                                       int ptr=i*2;
+                                                                                       //int ptr=i;
+                                                                                       int mono=_index[i];
+                                                                                       for(int j=0;j<bout;j++){
+                                                                                               int val=(int)(pcmf[i][mono+j]*32767.);
+                                                                                               if(val>32767){
+                                                                                                       val=32767;
+                                                                                               }
+                                                                                               if(val<-32768){
+                                                                                                       val=-32768;
+                                                                                               }
+                                                                                               if(val<0) val=val|0x8000;
+                                                                                               convbuffer[ptr]=(byte)(val);
+                                                                                               convbuffer[ptr+1]=(byte)(val>>>8);
+                                                                                               ptr+=2*(vi.channels);
+                                                                                       }
+                                                                               }
+                                                                               outputLine.write(convbuffer, 0, 2*vi.channels*bout);
+                                                                               vd.synthesis_read(bout);
+                                                                       }         
+                                                               }
+                                                       }
+                                                       if(og.eos()!=0)eos=1;
+                                               }
+                                       }
+
+                                       if(eos==0){
+                                               index=oy.buffer(BUFSIZE);
+                                               buffer=oy.data;
+                                               try{ bytes=bitStream.read(buffer,index,BUFSIZE); }
+                                               catch(Exception e){
+                                                       System.err.println(e);
+                                                       closeOutputLine();
+                                                       return;
+                                               }
+                                               if(bytes==-1){
+                                                       break;
+                                               }
+                                               oy.wrote(bytes);
+                                               if(bytes==0)eos=1;
+                                       }
+                               }
+
+                               os.clear();
+                               vb.clear();
+                               vd.clear();
+                               vi.clear();
+                       }
+
+               closeOutputLine();
+               oy.clear();
+               if(acontext!=null)
+                       acontext.showStatus("");
+
+               closeBitStream();
+       }
+
+       public void stop(){
+               if(player==null){
+                       closeOutputLine();
+                       closeBitStream();
+               }
+               player=null;
+       }
+
+       Vector playlist=new Vector();
+
+       public void actionPerformed(ActionEvent e){
+               String command=((JButton)(e.getSource())).getText();
+               if(command.equals(PLAY) && player==null){ 
+                       play_sound();
+               }
+               else if(player!=null){
+                       stop_sound();
+               }
+       }
+       
+       private void playFromOwnThread() {
+               synchronized(playThread) {
+                       playThread.notify();
+               }
+       }
+
+       private void playFromThisThread() {
+               if(player!=null) 
+                       return;
+               /*player=new Thread(this);
+               player.start();*/
+               // todo
+               start_button.setText(STOP); 
+               String item=getShoutSource();
+               if(item==null) {
+                       stop_sound();
+                       return;
+               }
+               bitStream=selectSource(item);
+               player = Thread.currentThread();
+               if(bitStream!=null){
+                       play_stream(player);
+               }
+               else System.out.println("Bitstream is null");
+               bitStream=null;
+               
+               stop_sound();
+       }
+
+
+       /* hooks */
+
+       public boolean isPlaying() {
+               return player != null;
+       }
+
+       public void play_sound(){
+               playFromOwnThread();
+       }
+
+       public void stop_sound(){
+               player=null;
+               start_button.setText(PLAY);
+
+       }
+       
+       InputStream selectSource(String item){
+               if(item.endsWith(".pls")){
+                       item=fetch_pls(item);
+                       if(item==null) return null;
+                       //System.out.println("fetch: "+item);
+               }
+               else if(item.endsWith(".m3u")){
+                       item=fetch_m3u(item);
+                       if(item==null)return null;
+                       //System.out.println("fetch: "+item);
+               }
+
+               if(!item.endsWith(".ogg")){
+                       return null;  
+               }
+
+               InputStream is=null;
+               URLConnection urlc=null;
+               try{
+                       URL url=null;
+                       url=new URL(getCodeBase(), item);
+                       urlc=url.openConnection();
+                       urlc.setRequestProperty("Pragma", "no-cache");
+                       urlc.setUseCaches(false);
+                       is=urlc.getInputStream();
+                       current_source=url.getProtocol()+"://"+url.getHost()+":"+url.getPort()+url.getFile();
+               }
+               catch(Exception ee){
+                       System.err.println(ee);             
+               }
+
+               if(is==null) {
+                       System.out.println("Selected input stream is null");
+                       return null;
+               }
+
+               System.out.println("Select: "+item);
+               return is;
+       }
+
+       String fetch_pls(String pls){
+               InputStream pstream=null;
+               if(pls.startsWith("http://")){
+                       try{
+                               URL url=null;
+                               url=new URL(getCodeBase(), pls);
+                                urlc=url.openConnection();
+                               pstream=urlc.getInputStream();
+                       }
+                       catch(Exception ee){
+                               System.err.println(ee);             
+                               return null;
+                       }
+               }
+
+               String line=null;
+               while(true){
+                       try{line=readline(pstream);}catch(Exception e){}
+                       if(line==null)break;
+                       if(line.startsWith("File1=")){
+                               byte[] foo=line.getBytes();
+                               int i=6;
+                               for(;i<foo.length; i++){
+                                       if(foo[i]==0x0d)break;
+                               }
+                               return line.substring(6, i);
+                       }
+               }
+               return null;
+       }
+
+       String fetch_m3u(String m3u){
+               InputStream pstream=null;
+               if(m3u.startsWith("http://")){
+                       try{
+                               URL url=null;
+                               url=new URL(getCodeBase(), m3u);
+                               
+                               URLConnection urlc=url.openConnection();
+                               pstream=urlc.getInputStream();
+                       }
+                       catch(Exception ee){
+                               System.err.println(ee);             
+                               return null;
+                       }
+               }
+
+               String line=null;
+               while(true){
+                       try{line=readline(pstream);}catch(Exception e){}
+                       if(line==null)break;
+                       return line;
+               }
+               return null;
+       }
+
+
+       void loadPlaylist(){
+               String s=null;
+               for(int i=0; i<10; i++){
+                       s=getParameter("jorbis.player.play."+i);
+                       System.out.println("Play" + i + ": " + s);
+                       if(s==null)
+                               break;
+                       playlist.addElement(s);
+               } 
+       }
+
+       private String readline(InputStream is) {
+               StringBuffer rtn=new StringBuffer();
+               int temp;
+               do {
+                       try { 
+                               temp=is.read();
+                       }
+                       catch(Exception e) {
+                               return null;
+                       }
+                       if(temp==-1) 
+                               return null;
+                       if(temp!=0 && temp!='\n')
+                               rtn.append((char)temp);
+               }while(temp!='\n');                                                        
+               return(rtn.toString());
+       }
+
+       public JOrbisPlayer(){
+       }
+
+       JPanel panel;
+       JButton start_button;
+
+       void initUI(){
+               panel=new JPanel();
+
+               start_button=new JButton(PLAY);
+               start_button.addActionListener(this);
+               panel.add(start_button);
+               panel.setBackground(bgColor);
+       }
+       
+       public String getShoutSource() {
+               try {
+                       return (String)playlist.firstElement();
+               }
+               catch(NoSuchElementException e) { 
+                       return null;
+               }
+       }
+       
+       /* since js don't have proper access right's we'll need a seperate watcher thread */
+       public class PlayWatch extends Thread {
+               
+               public PlayWatch() { }
+               
+               public void run() {
+                       while(true) {
+                               synchronized(this) {
+                                       try {
+                                               this.wait();
+                                       }catch (InterruptedException e) {}
+                               }
+                               playFromThisThread();
+                       }
+               }
+       }
+}
diff --git a/jorbis/src/com/jcraft/jogg/Buffer.java b/jorbis/src/com/jcraft/jogg/Buffer.java
new file mode 100644 (file)
index 0000000..a40a9de
--- /dev/null
@@ -0,0 +1,541 @@
+/* -*-mode:java; c-basic-offset:2; -*- */
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class Buffer{
+  private static final int BUFFER_INCREMENT=256;
+
+  private static final int[] mask={
+    0x00000000,0x00000001,0x00000003,0x00000007,0x0000000f,
+    0x0000001f,0x0000003f,0x0000007f,0x000000ff,0x000001ff,
+    0x000003ff,0x000007ff,0x00000fff,0x00001fff,0x00003fff,
+    0x00007fff,0x0000ffff,0x0001ffff,0x0003ffff,0x0007ffff,
+    0x000fffff,0x001fffff,0x003fffff,0x007fffff,0x00ffffff,
+    0x01ffffff,0x03ffffff,0x07ffffff,0x0fffffff,0x1fffffff,
+    0x3fffffff,0x7fffffff,0xffffffff
+  };
+
+  int ptr=0;
+  byte[] buffer=null;
+  int endbit=0;
+  int endbyte=0;
+  int storage=0;
+
+  public void writeinit(){
+    buffer=new byte[BUFFER_INCREMENT]; 
+    ptr=0;
+    buffer[0]=(byte)'\0';
+    storage=BUFFER_INCREMENT;
+  }
+
+  public void write(byte[] s){
+    for(int i=0; i<s.length; i++){
+      if(s[i]==0)break;
+      write(s[i],8);
+    }
+  }
+
+  public void read(byte[] s, int bytes){
+    int i=0;
+    while(bytes--!=0){
+      s[i++]=(byte)(read(8));
+    }
+  }
+
+  void reset(){
+    ptr=0;
+    buffer[0]=(byte)'\0';
+    endbit=endbyte=0;
+  }
+
+  public void writeclear(){
+    buffer=null;
+  }
+
+  public void readinit(byte[] buf, int bytes){
+    readinit(buf, 0, bytes);
+  }
+
+  public void readinit(byte[] buf, int start, int bytes){
+//System.err.println("readinit: start="+start+", bytes="+bytes);
+//for(int i=0;i<bytes; i++){
+//System.err.println(i+": "+Integer.toHexString(buf[i+start]));
+//}
+    ptr=start;
+    buffer=buf;
+    endbit=endbyte=0;
+    storage=bytes;
+  }
+
+  public void write(int value, int bits){
+//System.err.println("write: "+Integer.toHexString(value)+", bits="+bits+" ptr="+ptr+", storage="+storage+", endbyte="+endbyte);
+    if(endbyte+4>=storage){
+      byte[] foo=new byte[storage+BUFFER_INCREMENT];
+      System.arraycopy(buffer, 0, foo, 0, storage);
+      buffer=foo;
+      storage+=BUFFER_INCREMENT;
+    }
+
+    value&=mask[bits];
+    bits+=endbit;
+    buffer[ptr]|=(byte)(value<<endbit);
+
+    if(bits>=8){
+      buffer[ptr+1]=(byte)(value>>>(8-endbit));
+      if(bits>=16){
+        buffer[ptr+2]=(byte)(value>>>(16-endbit));  
+        if(bits>=24){
+         buffer[ptr+3]=(byte)(value>>>(24-endbit));  
+          if(bits>=32){
+           if(endbit>0)
+             buffer[ptr+4]=(byte)(value>>>(32-endbit));
+           else
+             buffer[ptr+4]=0;
+         }
+        }
+      }
+    }
+
+    endbyte+=bits/8;
+    ptr+=bits/8;
+    endbit=bits&7;
+  }
+
+  public int look(int bits){
+    int ret;
+    int m=mask[bits];
+
+    bits+=endbit;
+
+//System.err.println("look ptr:"+ptr+", bits="+bits+", endbit="+endbit+", storage="+storage);
+
+    if(endbyte+4>=storage){
+      if(endbyte+(bits-1)/8>=storage)return(-1);
+    }
+  
+    ret=((buffer[ptr])&0xff)>>>endbit;
+//  ret=((byte)(buffer[ptr]))>>>endbit;
+    if(bits>8){
+    ret|=((buffer[ptr+1])&0xff)<<(8-endbit);
+//      ret|=((byte)(buffer[ptr+1]))<<(8-endbit);
+      if(bits>16){
+      ret|=((buffer[ptr+2])&0xff)<<(16-endbit);
+//        ret|=((byte)(buffer[ptr+2]))<<(16-endbit);
+        if(bits>24){
+         ret|=((buffer[ptr+3])&0xff)<<(24-endbit);
+//System.err.print("ret="+Integer.toHexString(ret)+", ((byte)(buffer[ptr+3]))="+Integer.toHexString(((buffer[ptr+3])&0xff)));
+//       ret|=((byte)(buffer[ptr+3]))<<(24-endbit);
+//System.err.println(" ->ret="+Integer.toHexString(ret));
+         if(bits>32 && endbit!=0){
+           ret|=((buffer[ptr+4])&0xff)<<(32-endbit);
+//         ret|=((byte)(buffer[ptr+4]))<<(32-endbit);
+         }
+        }
+      }
+    }
+    return(m&ret);
+  }
+
+  public int look1(){
+    if(endbyte>=storage)return(-1);
+    return((buffer[ptr]>>endbit)&1);
+  }
+
+  public void adv(int bits){
+    bits+=endbit;
+    ptr+=bits/8;
+    endbyte+=bits/8;
+    endbit=bits&7;
+  }
+
+  public void adv1(){
+    ++endbit;
+    if(endbit>7){
+      endbit=0;
+      ptr++;
+      endbyte++;
+    }
+  }
+
+  public int read(int bits){
+//System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte);
+//System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte+
+//                        ", ptr="+ptr+", endbit="+endbit+", buf[ptr]="+buffer[ptr]);
+
+    int ret;
+    int m=mask[bits];
+
+    bits+=endbit;
+
+    if(endbyte+4>=storage){
+      ret=-1;
+      if(endbyte+(bits-1)/8>=storage){
+        ptr+=bits/8;
+        endbyte+=bits/8;
+        endbit=bits&7;
+        return(ret);
+      }
+    }
+
+/*  
+    ret=(byte)(buffer[ptr]>>>endbit);
+    if(bits>8){
+      ret|=(buffer[ptr+1]<<(8-endbit));  
+      if(bits>16){
+        ret|=(buffer[ptr+2]<<(16-endbit));  
+        if(bits>24){
+         ret|=(buffer[ptr+3]<<(24-endbit));  
+         if(bits>32 && endbit>0){
+           ret|=(buffer[ptr+4]<<(32-endbit));
+         }
+        }
+      }
+    }
+*/
+    ret=((buffer[ptr])&0xff)>>>endbit;
+    if(bits>8){
+    ret|=((buffer[ptr+1])&0xff)<<(8-endbit);
+//      ret|=((byte)(buffer[ptr+1]))<<(8-endbit);
+      if(bits>16){
+      ret|=((buffer[ptr+2])&0xff)<<(16-endbit);
+//        ret|=((byte)(buffer[ptr+2]))<<(16-endbit);
+        if(bits>24){
+         ret|=((buffer[ptr+3])&0xff)<<(24-endbit);
+//       ret|=((byte)(buffer[ptr+3]))<<(24-endbit);
+         if(bits>32 && endbit!=0){
+           ret|=((buffer[ptr+4])&0xff)<<(32-endbit);
+//         ret|=((byte)(buffer[ptr+4]))<<(32-endbit);
+         }
+        }
+      }
+    }
+
+    ret&=m;
+
+    ptr+=bits/8;
+//    ptr=bits/8;
+    endbyte+=bits/8;
+//    endbyte=bits/8;
+    endbit=bits&7;
+    return(ret);
+  }
+
+  public int readB(int bits){
+    //System.err.println(this+" read: bits="+bits+", storage="+storage+", endbyte="+endbyte+
+    //                        ", ptr="+ptr+", endbit="+endbit+", buf[ptr]="+buffer[ptr]);
+    int ret;
+    int m=32-bits;
+
+    bits+=endbit;
+
+    if(endbyte+4>=storage){
+      /* not the main path */
+      ret=-1;
+      if(endbyte*8+bits>storage*8) {
+       ptr+=bits/8;
+       endbyte+=bits/8;
+       endbit=bits&7;
+       return(ret);
+      }
+    }
+
+    ret=(buffer[ptr]&0xff)<<(24+endbit);
+    if(bits>8){
+      ret|=(buffer[ptr+1]&0xff)<<(16+endbit);
+      if(bits>16){
+       ret|=(buffer[ptr+2]&0xff)<<(8+endbit);
+       if(bits>24){
+         ret|=(buffer[ptr+3]&0xff)<<(endbit);
+         if(bits>32 && (endbit != 0))
+           ret|=(buffer[ptr+4]&0xff)>>(8-endbit);
+       }
+      }
+    }
+    ret=(ret>>>(m>>1))>>>((m+1)>>1);
+
+    ptr+=bits/8;
+    endbyte+=bits/8;
+    endbit=bits&7;
+    return(ret);
+  }
+
+  public int read1(){
+    int ret;
+    if(endbyte>=storage){
+      ret=-1;
+      endbit++;
+      if(endbit>7){
+        endbit=0;
+        ptr++;
+        endbyte++;
+      }
+      return(ret);
+    }
+
+    ret=(buffer[ptr]>>endbit)&1;
+
+    endbit++;
+    if(endbit>7){
+      endbit=0;
+      ptr++;
+      endbyte++;
+    }
+    return(ret);
+  }
+
+  public int bytes(){
+    return(endbyte+(endbit+7)/8);
+  }
+
+  public int bits(){
+    return(endbyte*8+endbit);
+  }
+
+  public byte[] buffer(){
+    return(buffer);
+  }
+
+  public static int ilog(int v){
+    int ret=0;
+    while(v>0){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+  public static void report(String in){
+    System.err.println(in);
+    System.exit(1);
+  }
+
+  /*
+  static void cliptest(int[] b, int vals, int bits, int[] comp, int compsize){
+    int bytes;
+    byte[] buffer;
+
+    o.reset();
+    for(int i=0;i<vals;i++){
+      o.write(b[i],((bits!=0)?bits:ilog(b[i])));
+    }
+    buffer=o.buffer();
+    bytes=o.bytes();
+System.err.println("cliptest: bytes="+bytes);
+    if(bytes!=compsize)report("wrong number of bytes!\n");
+    for(int i=0;i<bytes;i++){
+      if(buffer[i]!=(byte)comp[i]){
+        for(int j=0;j<bytes;j++){
+          System.err.println(j+": "+Integer.toHexString(buffer[j])+" "+
+                             Integer.toHexString(comp[j]));
+       }
+       report("wrote incorrect value!\n");
+      }
+    }
+System.err.println("bits: "+bits);
+    r.readinit(buffer,bytes);
+    for(int i=0;i<vals;i++){
+      int tbit=(bits!=0)?bits:ilog(b[i]);
+System.err.println(Integer.toHexString(b[i])+" tbit: "+tbit); 
+      if(r.look(tbit)==-1){
+        report("out of data!\n");
+      }
+      if(r.look(tbit)!=(b[i]&mask[tbit])){
+        report(i+" looked at incorrect value! "+Integer.toHexString(r.look(tbit))+", "+Integer.toHexString(b[i]&mask[tbit])+":"+b[i]+" bit="+tbit);
+      }
+      if(tbit==1){
+        if(r.look1()!=(b[i]&mask[tbit])){
+         report("looked at single bit incorrect value!\n");
+       }
+      }
+      if(tbit==1){
+        if(r.read1()!=(b[i]&mask[tbit])){
+         report("read incorrect single bit value!\n");
+       }
+      }
+      else{
+       if(r.read(tbit)!=(b[i]&mask[tbit])){
+         report("read incorrect value!\n");
+       }
+      }
+    }
+    if(r.bytes()!=bytes){
+      report("leftover bytes after read!\n");
+    }
+  }
+
+  static int[] testbuffer1=
+    {18,12,103948,4325,543,76,432,52,3,65,4,56,32,42,34,21,1,23,32,546,456,7,
+       567,56,8,8,55,3,52,342,341,4,265,7,67,86,2199,21,7,1,5,1,4};
+  static int test1size=43;
+
+  static int[] testbuffer2=
+    {216531625,1237861823,56732452,131,3212421,12325343,34547562,12313212,
+       1233432,534,5,346435231,14436467,7869299,76326614,167548585,
+       85525151,0,12321,1,349528352};
+  static int test2size=21;
+
+  static int[] large=
+    {2136531625,2137861823,56732452,131,3212421,12325343,34547562,12313212,
+       1233432,534,5,2146435231,14436467,7869299,76326614,167548585,
+       85525151,0,12321,1,2146528352};
+
+  static int[] testbuffer3=
+    {1,0,14,0,1,0,12,0,1,0,0,0,1,1,0,1,0,1,0,1,0,1,0,1,0,1,0,0,1,1,1,1,1,0,0,1,
+       0,1,30,1,1,1,0,0,1,0,0,0,12,0,11,0,1,0,0,1};
+  static int test3size=56;
+
+  static int onesize=33;
+  static int[] one={146,25,44,151,195,15,153,176,233,131,196,65,85,172,47,40,
+                    34,242,223,136,35,222,211,86,171,50,225,135,214,75,172,
+                    223,4};
+
+  static int twosize=6;
+  static int[] two={61,255,255,251,231,29};
+
+  static int threesize=54;
+  static int[] three={169,2,232,252,91,132,156,36,89,13,123,176,144,32,254,
+                      142,224,85,59,121,144,79,124,23,67,90,90,216,79,23,83,
+                      58,135,196,61,55,129,183,54,101,100,170,37,127,126,10,
+                      100,52,4,14,18,86,77,1};
+
+  static int foursize=38;
+  static int[] four={18,6,163,252,97,194,104,131,32,1,7,82,137,42,129,11,72,
+                     132,60,220,112,8,196,109,64,179,86,9,137,195,208,122,169,
+                     28,2,133,0,1};
+
+  static int fivesize=45;
+  static int[] five={169,2,126,139,144,172,30,4,80,72,240,59,130,218,73,62,
+                     241,24,210,44,4,20,0,248,116,49,135,100,110,130,181,169,
+                     84,75,159,2,1,0,132,192,8,0,0,18,22};
+
+  static int sixsize=7;
+  static int[] six={17,177,170,242,169,19,148};
+
+  static Buffer o=new Buffer();
+  static Buffer r=new Buffer();
+
+  public static void main(String[] arg){
+    byte[] buffer;
+    int bytes;
+//  o=new Buffer();
+//  r=new Buffer();
+
+    o.writeinit();
+
+    System.err.print("\nSmall preclipped packing: ");
+    cliptest(testbuffer1,test1size,0,one,onesize);
+    System.err.print("ok.");
+
+    System.err.print("\nNull bit call: ");
+    cliptest(testbuffer3,test3size,0,two,twosize);
+    System.err.print("ok.");
+
+    System.err.print("\nLarge preclipped packing: ");
+    cliptest(testbuffer2,test2size,0,three,threesize);
+    System.err.print("ok.");
+
+    System.err.print("\n32 bit preclipped packing: ");
+    o.reset();
+    for(int i=0;i<test2size;i++)
+      o.write(large[i],32);
+    buffer=o.buffer();
+    bytes=o.bytes();
+
+
+    r.readinit(buffer,bytes);
+    for(int i=0;i<test2size;i++){
+      if(r.look(32)==-1){
+       report("out of data. failed!");
+      }
+      if(r.look(32)!=large[i]){
+       System.err.print(r.look(32)+" != "+large[i]+" ("+
+                         Integer.toHexString(r.look(32))+"!="+
+                         Integer.toHexString(large[i])+")");
+       report("read incorrect value!\n");
+      }
+      r.adv(32);
+    }
+    if(r.bytes()!=bytes)report("leftover bytes after read!\n");
+    System.err.print("ok.");
+
+    System.err.print("\nSmall unclipped packing: ");
+    cliptest(testbuffer1,test1size,7,four,foursize);
+    System.err.print("ok.");
+
+    System.err.print("\nLarge unclipped packing: ");
+    cliptest(testbuffer2,test2size,17,five,fivesize);
+    System.err.print("ok.");
+
+    System.err.print("\nSingle bit unclicpped packing: ");
+    cliptest(testbuffer3,test3size,1,six,sixsize);
+    System.err.print("ok.");
+
+    System.err.print("\nTesting read past end: ");
+    r.readinit("\0\0\0\0\0\0\0\0".getBytes(),8);
+    for(int i=0;i<64;i++){
+      if(r.read(1)!=0){
+       System.err.print("failed; got -1 prematurely.\n");
+       System.exit(1);
+      }
+    }
+
+    if(r.look(1)!=-1 ||
+       r.read(1)!=-1){
+      System.err.print("failed; read past end without -1.\n");
+      System.exit(1);
+    }
+
+    r.readinit("\0\0\0\0\0\0\0\0".getBytes(),8);
+    if(r.read(30)!=0 || r.read(16)!=0){
+      System.err.print("failed 2; got -1 prematurely.\n");
+    System.exit(1);
+    }
+
+    if(r.look(18)!=0 ||
+       r.look(18)!=0){
+      System.err.print("failed 3; got -1 prematurely.\n");
+      System.exit(1);
+    }
+    if(r.look(19)!=-1 ||
+       r.look(19)!=-1){
+      System.err.print("failed; read past end without -1.\n");
+      System.exit(1);
+    }
+    if(r.look(32)!=-1 ||
+       r.look(32)!=-1){
+      System.err.print("failed; read past end without -1.\n");
+      System.exit(1);
+    }
+    System.err.print("ok.\n\n");
+  }
+  */
+}
+
+
+
+
+
diff --git a/jorbis/src/com/jcraft/jogg/Packet.java b/jorbis/src/com/jcraft/jogg/Packet.java
new file mode 100644 (file)
index 0000000..22a8a54
--- /dev/null
@@ -0,0 +1,82 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class Packet{
+  public byte[] packet_base;
+  public int packet;
+  public int bytes;
+  public int b_o_s;
+  public int e_o_s;
+
+  public long granulepos;
+
+  public long packetno; // sequence number for decode; the framing
+                       // knows where there's a hole in the data,
+                       // but we need coupling so that the codec
+                       // (which is in a seperate abstraction
+                       // layer) also knows about the gap
+
+  /*
+  // TEST
+  static int sequence=0;
+  static int lastno=0;
+  void checkpacket(int len, int no, int pos){
+    if(bytes!=len){
+      System.err.println("incorrect packet length!");
+      System.exit(1);
+    }
+    if(granulepos!=pos){
+      System.err.println("incorrect packet position!");
+      System.exit(1);
+    }
+
+    // packet number just follows sequence/gap; adjust the input number
+    // for that
+    if(no==0){
+      sequence=0;
+    }
+    else{
+      sequence++;
+      if(no>lastno+1)
+       sequence++;
+    }
+    lastno=no;
+    if(packetno!=sequence){
+     System.err.println("incorrect packet sequence "+packetno+" != "+sequence);
+      System.exit(1);
+    }
+
+    // Test data
+    for(int j=0;j<bytes;j++){
+      if((packet_base[packet+j]&0xff)!=((j+no)&0xff)){
+       System.err.println("body data mismatch at pos "+ j+": "+(packet_base[packet+j]&0xff)+"!="+((j+no)&0xff)+"!\n");
+       System.exit(1);
+      }
+    }
+  }
+  */
+}
diff --git a/jorbis/src/com/jcraft/jogg/Page.java b/jorbis/src/com/jcraft/jogg/Page.java
new file mode 100644 (file)
index 0000000..fc1add0
--- /dev/null
@@ -0,0 +1,973 @@
+/* -*-mode:java; c-basic-offset:2; -*- */
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class Page{
+  private static int[] crc_lookup=new int[256];
+  static {
+    for(int i=0; i<crc_lookup.length; i++){
+      crc_lookup[i]=crc_entry(i);
+    }
+  }
+
+  private static int crc_entry(int index){
+    int r=index<<24;
+    for(int i=0; i<8; i++){
+      if((r& 0x80000000)!=0){
+        r=(r << 1)^0x04c11db7; /* The same as the ethernet generator
+                                 polynomial, although we use an
+                                 unreflected alg and an init/final
+                                 of 0, not 0xffffffff */
+      }
+      else{
+       r<<=1;
+      }
+    }
+    return(r&0xffffffff);
+  }
+
+  public byte[] header_base;
+  public int header;
+  public int header_len;
+  public byte[] body_base;
+  public int body;
+  public int body_len;
+
+  int version(){
+    return header_base[header+4]&0xff;
+  }
+  int continued(){
+    return (header_base[header+5]&0x01);
+  }
+  public int bos(){
+    return (header_base[header+5]&0x02);
+  }
+  public int eos(){
+    return (header_base[header+5]&0x04);
+  }
+  public long granulepos(){
+    long foo=header_base[header+13]&0xff;
+    foo=(foo<<8)|(header_base[header+12]&0xff);
+    foo=(foo<<8)|(header_base[header+11]&0xff);
+    foo=(foo<<8)|(header_base[header+10]&0xff);
+    foo=(foo<<8)|(header_base[header+9]&0xff);
+    foo=(foo<<8)|(header_base[header+8]&0xff);
+    foo=(foo<<8)|(header_base[header+7]&0xff);
+    foo=(foo<<8)|(header_base[header+6]&0xff);
+    return(foo);
+  }
+  public int serialno(){
+    return (header_base[header+14]&0xff)|
+           ((header_base[header+15]&0xff)<<8)|
+           ((header_base[header+16]&0xff)<<16)|
+           ((header_base[header+17]&0xff)<<24);
+  }
+  int pageno(){
+    return (header_base[header+18]&0xff)|
+           ((header_base[header+19]&0xff)<<8)|
+           ((header_base[header+20]&0xff)<<16)|
+           ((header_base[header+21]&0xff)<<24);
+  }
+
+  void checksum(){
+    int crc_reg=0;
+
+//  for(int i=0;i<header_len;i++){
+//    System.err.println("chksum: "+Integer.toHexString(header_base[header+i]&0xff));
+//  }
+    
+    for(int i=0;i<header_len;i++){
+      crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg>>>24)&0xff)^(header_base[header+i]&0xff)];
+    }
+    for(int i=0;i<body_len;i++){
+      crc_reg=(crc_reg<<8)^crc_lookup[((crc_reg>>>24)&0xff)^(body_base[body+i]&0xff)];
+    }
+    header_base[header+22]=(byte)crc_reg/*&0xff*/;
+    header_base[header+23]=(byte)(crc_reg>>>8)/*&0xff*/;
+    header_base[header+24]=(byte)(crc_reg>>>16)/*&0xff*/;
+    header_base[header+25]=(byte)(crc_reg>>>24)/*&0xff*/;
+  }
+  public Page copy(){
+    return copy(new Page());
+  }
+  public Page copy(Page p){
+    byte[] tmp=new byte[header_len];
+    System.arraycopy(header_base, header, tmp, 0, header_len);
+    p.header_len=header_len;
+    p.header_base=tmp;
+    p.header=0;
+    tmp=new byte[body_len];
+    System.arraycopy(body_base, body, tmp, 0, body_len);
+    p.body_len=body_len;
+    p.body_base=tmp;
+    p.body=0;
+    return p;
+  }
+  /*
+  // TEST
+  static StreamState os_en, os_de;
+  static SyncState oy;
+  void check_page(byte[] data_base, int data, int[] _header){
+    // Test data
+    for(int j=0;j<body_len;j++)
+      if(body_base[body+j]!=data_base[data+j]){
+       System.err.println("body data mismatch at pos "+j+": "+data_base[data+j]+"!="+body_base[body+j]+"!\n");
+      System.exit(1);
+    }
+
+    // Test header
+    for(int j=0;j<header_len;j++){
+      if((header_base[header+j]&0xff)!=_header[j]){
+       System.err.println("header content mismatch at pos "+j);
+       for(int jj=0;jj<_header[26]+27;jj++)
+         System.err.print(" ("+jj+")"+Integer.toHexString(_header[jj])+":"+Integer.toHexString(header_base[header+jj]));
+       System.err.println("");
+       System.exit(1);
+      }
+    }
+    if(header_len!=_header[26]+27){
+      System.err.print("header length incorrect! ("+header_len+"!="+(_header[26]+27)+")");
+      System.exit(1);
+    }
+  }
+
+  void print_header(){
+    System.err.println("\nHEADER:");
+    System.err.println("  capture: "+
+                      (header_base[header+0]&0xff)+" "+
+                      (header_base[header+1]&0xff)+" "+
+                      (header_base[header+2]&0xff)+" "+
+                      (header_base[header+3]&0xff)+" "+
+                       " version: "+(header_base[header+4]&0xff)+"  flags: "+
+                      (header_base[header+5]&0xff));
+    System.err.println("  pcmpos: "+
+                      (((header_base[header+9]&0xff)<<24)|
+                       ((header_base[header+8]&0xff)<<16)|
+                       ((header_base[header+7]&0xff)<<8)|
+                       ((header_base[header+6]&0xff)))+
+                      "  serialno: "+
+                      (((header_base[header+17]&0xff)<<24)|
+                       ((header_base[header+16]&0xff)<<16)|
+                       ((header_base[header+15]&0xff)<<8)|
+                       ((header_base[header+14]&0xff)))+
+                      "  pageno: "+
+                      (((header_base[header+21]&0xff)<<24)|
+                       ((header_base[header+20]&0xff)<<16)|
+                       ((header_base[header+19]&0xff)<<8)|
+                       ((header_base[header+18]&0xff))));
+
+    System.err.println("  checksum: "+
+                      (header_base[header+22]&0xff)+":"+
+                      (header_base[header+23]&0xff)+":"+
+                      (header_base[header+24]&0xff)+":"+
+                      (header_base[header+25]&0xff)+"\n  segments: "+
+                      (header_base[header+26]&0xff)+" (");
+    for(int j=27;j<header_len;j++){
+      System.err.println((header_base[header+j]&0xff)+" ");
+    }
+    System.err.println(")\n");
+  }
+
+  void copy_page(){
+    byte[] tmp=new byte[header_len];
+    System.arraycopy(header_base, header, tmp, 0, header_len);
+    header_base=tmp;
+    header=0;
+    tmp=new byte[body_len];
+    System.arraycopy(body_base, body, tmp, 0, body_len);
+    body_base=tmp;
+    body=0;
+  }
+
+  static void test_pack(int[] pl, int[][] headers){
+    byte[] data=new byte[1024*1024]; // for scripted test cases only
+    int inptr=0;
+    int outptr=0;
+    int deptr=0;
+    int depacket=0;
+    int pcm_pos=7;
+    int packets,pageno=0,pageout=0;
+    int eosflag=0;
+    int bosflag=0;
+
+    os_en.reset();
+    os_de.reset();
+    oy.reset();
+
+    for(packets=0;;packets++){
+      if(pl[packets]==-1)break;
+    }
+
+    for(int i=0;i<packets;i++){
+      // construct a test packet
+      Packet op=new Packet();
+      int len=pl[i];
+      op.packet_base=data;
+      op.packet=inptr;
+      op.bytes=len;
+      op.e_o_s=(pl[i+1]<0?1:0);
+      op.granulepos=pcm_pos;
+
+      pcm_pos+=1024;
+
+      for(int j=0;j<len;j++){
+       data[inptr++]=(byte)(i+j);
+      }
+
+      // submit the test packet
+      os_en.packetin(op);
+
+      // retrieve any finished pages
+      {
+       Page og=new Page();
+      
+       while(os_en.pageout(og)!=0){
+         // We have a page.  Check it carefully
+         //System.err.print(pageno+", ");
+         if(headers[pageno]==null){
+           System.err.println("coded too many pages!");
+           System.exit(1);
+         }
+         og.check_page(data, outptr, headers[pageno]);
+
+         outptr+=og.body_len;
+         pageno++;
+
+//System.err.println("1# pageno="+pageno+", pageout="+pageout);
+
+         // have a complete page; submit it to sync/decode
+         
+         {
+           Page og_de=new Page();
+           Packet op_de=new Packet();
+           int index=oy.buffer(og.header_len+og.body_len);
+           byte[] buf=oy.data;
+           System.arraycopy(og.header_base, og.header, buf, index, og.header_len);
+           System.arraycopy(og.body_base, og.body, buf, index+og.header_len, og.body_len);
+           oy.wrote(og.header_len+og.body_len);
+
+//System.err.println("2# pageno="+pageno+", pageout="+pageout);
+
+           while(oy.pageout(og_de)>0){
+             // got a page.  Happy happy.  Verify that it's good.
+           
+             og_de.check_page(data, deptr, headers[pageout]);
+             deptr+=og_de.body_len;
+             pageout++;
+
+             // submit it to deconstitution
+             os_de.pagein(og_de);
+
+             // packets out?
+             while(os_de.packetout(op_de)>0){
+             
+               // verify the packet!
+               // check data
+               boolean check=false;
+               for(int ii=0; ii<op_de.bytes; ii++){
+                 if(data[depacket+ii]!=op_de.packet_base[op_de.packet+ii]){
+                   check=true;
+                   break;
+                 }
+               }
+               if(check){
+                 System.err.println("packet data mismatch in decode! pos="+
+                                    depacket);
+                 System.exit(1);
+               }
+
+               // check bos flag
+               if(bosflag==0 && op_de.b_o_s==0){
+                 System.err.println("b_o_s flag not set on packet!");
+                 System.exit(1);
+               }
+               if(bosflag!=0 && op_de.b_o_s!=0){
+                 System.err.println("b_o_s flag incorrectly set on packet!");
+                 System.exit(1);
+               }
+             
+               bosflag=1;
+               depacket+=op_de.bytes;
+             
+               // check eos flag
+               if(eosflag!=0){
+                 System.err.println("Multiple decoded packets with eos flag!");
+                 System.exit(1);
+               }
+
+               if(op_de.e_o_s!=0)eosflag=1;
+
+               // check pcmpos flag
+               if(op_de.granulepos!=-1){
+                 System.err.print(" pcm:"+op_de.granulepos+" ");
+               }
+             }
+           }
+         }
+       }
+      }
+    }
+    //free(data);
+    if(headers[pageno]!=null){
+      System.err.println("did not write last page!");
+      System.exit(1);
+    }
+    if(headers[pageout]!=null){
+      System.err.println("did not decode last page!");
+      System.exit(1);
+    }
+    if(inptr!=outptr){
+      System.err.println("encoded page data incomplete!");
+      System.exit(1);
+    }
+    if(inptr!=deptr){
+      System.err.println("decoded page data incomplete!");
+      System.exit(1);
+    }
+    if(inptr!=depacket){
+      System.err.println("decoded packet data incomplete!");
+      System.exit(1);
+    }
+    if(eosflag==0){
+      System.err.println("Never got a packet with EOS set!");
+    }
+    System.err.println("ok.");
+  }
+
+  static void error(){
+    System.err.println("error!");
+    System.exit(1);
+  }
+  public static void main(String[] arg){
+
+    os_en=new StreamState(0x04030201);
+    os_de=new StreamState(0x04030201);
+
+    oy=new SyncState();
+
+    // Exercise each code path in the framing code.  Also verify that
+    // the checksums are working.
+
+    {
+      // 17 only
+      int[] packets={17, -1};
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x06,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0x15,0xed,0xec,0x91,
+                  1,
+                  17};
+      int[][] headret={head1, null};
+    
+      System.err.print("testing single page encoding... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // 17, 254, 255, 256, 500, 510, 600 byte, pad
+      int[] packets={17, 254, 255, 256, 500, 510, 600, -1};
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0x59,0x10,0x6c,0x2c,
+                  1,
+                   17};
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x04,
+                  0x07,0x18,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0x89,0x33,0x85,0xce,
+                  13,
+                  254,255,0,255,1,255,245,255,255,0,
+                  255,255,90};
+      int[][] headret={head1,head2,null};
+
+      System.err.print("testing basic page encoding... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // nil packets; beginning,middle,end
+      int[] packets={0,17, 254, 255, 0, 256, 0, 500, 510, 600, 0, -1};
+
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0xff,0x7b,0x23,0x17,
+                  1,
+                  0};
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x04,
+                  0x07,0x28,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0x5c,0x3f,0x66,0xcb,
+                  17,
+                  17,254,255,0,0,255,1,0,255,245,255,255,0,
+                  255,255,90,0};
+      int[][] headret={head1,head2,null};
+
+      System.err.print("testing basic nil packets... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // large initial packet
+      int[] packets={4345,259,255,-1};
+
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0x01,0x27,0x31,0xaa,
+                  18,
+                  255,255,255,255,255,255,255,255,
+                  255,255,255,255,255,255,255,255,255,10};
+
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x04,
+                  0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0x7f,0x4e,0x8a,0xd2,
+                  4,
+                  255,4,255,0};
+      int[][] headret={head1,head2,null};
+
+      System.err.print("testing initial-packet lacing > 4k... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // continuing packet test
+      int[] packets={0,4345,259,255,-1};
+
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0xff,0x7b,0x23,0x17,
+                  1,
+                  0};
+
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+                  0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0x34,0x24,0xd5,0x29,
+                  17,
+                  255,255,255,255,255,255,255,255,
+                  255,255,255,255,255,255,255,255,255};
+
+      int[] head3={0x4f,0x67,0x67,0x53,0,0x05,
+                  0x07,0x0c,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,2,0,0,0,
+                  0xc8,0xc3,0xcb,0xed,
+                  5,
+                  10,255,4,255,0};
+      int[][] headret={head1,head2,head3,null};
+
+      System.err.print("testing single packet page span... ");
+      test_pack(packets,headret);
+    }
+
+    // page with the 255 segment limit
+    {
+
+      int[] packets={0,10,10,10,10,10,10,10,10,
+                    10,10,10,10,10,10,10,10,
+                    10,10,10,10,10,10,10,10,
+                    10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,50,-1};
+
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0xff,0x7b,0x23,0x17,
+                  1,
+                  0};
+
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+                  0x07,0xfc,0x03,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0xed,0x2a,0x2e,0xa7,
+                  255,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10,10,
+                  10,10,10,10,10,10,10};
+
+      int[] head3={0x4f,0x67,0x67,0x53,0,0x04,
+                  0x07,0x00,0x04,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,2,0,0,0,
+                  0x6c,0x3b,0x82,0x3d,
+                  1,
+                  50};
+      int[][] headret={head1,head2,head3,null};
+
+      System.err.print("testing max packet segments... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // packet that overspans over an entire page
+
+      int[] packets={0,100,9000,259,255,-1};
+
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0xff,0x7b,0x23,0x17,
+                  1,
+                  0};
+
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+                  0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0x3c,0xd9,0x4d,0x3f,
+                  17,
+                  100,255,255,255,255,255,255,255,255,
+                  255,255,255,255,255,255,255,255};
+
+      int[] head3={0x4f,0x67,0x67,0x53,0,0x01,
+                  0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,2,0,0,0,
+                  0xbd,0xd5,0xb5,0x8b,
+                  17,
+                  255,255,255,255,255,255,255,255,
+                  255,255,255,255,255,255,255,255,255};
+
+      int[] head4={0x4f,0x67,0x67,0x53,0,0x05,
+                  0x07,0x10,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,3,0,0,0,
+                  0xef,0xdd,0x88,0xde,
+                  7,
+                  255,255,75,255,4,255,0};
+      int[][] headret={head1,head2,head3,head4,null};
+
+      System.err.print("testing very large packets... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // term only page.  why not?
+
+      int[] packets={0,100,4080,-1};
+
+      int[] head1={0x4f,0x67,0x67,0x53,0,0x02,
+                  0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,0,0,0,0,
+                  0xff,0x7b,0x23,0x17,
+                  1,
+                  0};
+
+      int[] head2={0x4f,0x67,0x67,0x53,0,0x00,
+                  0x07,0x04,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,1,0,0,0,
+                  0x3c,0xd9,0x4d,0x3f,
+                  17,
+                  100,255,255,255,255,255,255,255,255,
+                  255,255,255,255,255,255,255,255};
+
+      int[] head3={0x4f,0x67,0x67,0x53,0,0x05,
+                  0x07,0x08,0x00,0x00,0x00,0x00,0x00,0x00,
+                  0x01,0x02,0x03,0x04,2,0,0,0,
+                  0xd4,0xe0,0x60,0xe5,
+                  1,0};
+
+      int[][] headret={head1,head2,head3,null};
+
+      System.err.print("testing zero data page (1 nil packet)... ");
+      test_pack(packets,headret);
+    }
+
+    {
+      // build a bunch of pages for testing
+      byte[] data=new byte[1024*1024];
+      int[] pl={0,100,4079,2956,2057,76,34,912,0,234,1000,1000,1000,300,-1};
+      int inptr=0;
+      Page[] og=new Page[5];
+      for(int i=0; i<5; i++){
+       og[i]=new Page();
+      }
+    
+      os_en.reset();
+
+      for(int i=0;pl[i]!=-1;i++){
+       Packet op=new Packet();
+       int len=pl[i];
+      
+       op.packet_base=data;
+       op.packet=inptr;
+       op.bytes=len;
+       op.e_o_s=(pl[i+1]<0?1:0);
+       op.granulepos=(i+1)*1000;
+       
+       for(int j=0;j<len;j++)data[inptr++]=(byte)(i+j);
+       os_en.packetin(op);
+      }    
+
+//    free(data);
+
+      // retrieve finished pages
+      for(int i=0;i<5;i++){
+       if(os_en.pageout(og[i])==0){
+         System.err.print("Too few pages output building sync tests!\n");
+         System.exit(1);
+       }
+       og[i].copy_page();
+      }
+
+      // Test lost pages on pagein/packetout: no rollback
+      {
+       Page temp=new Page();
+       Packet test=new Packet();
+
+       System.err.print("Testing loss of pages... ");
+
+       oy.reset();
+       os_de.reset();
+       for(int i=0;i<5;i++){
+         int index=oy.buffer(og[i].header_len);
+         System.arraycopy(og[i].header_base, og[i].header,
+                          oy.data, index, og[i].header_len);
+         oy.wrote(og[i].header_len);
+         index=oy.buffer(og[i].body_len);
+         System.arraycopy(og[i].body_base, og[i].body,
+                          oy.data, index, og[i].body_len);
+         oy.wrote(og[i].body_len);
+       }
+
+       oy.pageout(temp);
+       os_de.pagein(temp);
+       oy.pageout(temp);
+       os_de.pagein(temp);
+       oy.pageout(temp);
+
+        // skip
+       oy.pageout(temp);
+       os_de.pagein(temp);
+
+        // do we get the expected results/packets?
+      
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(0,0,0);
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(100,1,-1);
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(4079,2,3000);
+       if(os_de.packetout(test)!=-1){
+         System.err.println("Error: loss of page did not return error");
+         System.exit(1);
+       }
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(76,5,-1);
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(34,6,-1);
+       System.err.println("ok.");
+      }
+
+      // Test lost pages on pagein/packetout: rollback with continuation
+      {
+       Page temp=new Page();
+       Packet test=new Packet();
+
+       System.err.print("Testing loss of pages (rollback required)... ");
+
+       oy.reset();
+       os_de.reset();
+       for(int i=0;i<5;i++){
+         int index=oy.buffer(og[i].header_len);
+         System.arraycopy(og[i].header_base, og[i].header,
+                          oy.data, index, og[i].header_len);
+         oy.wrote(og[i].header_len);
+         index=oy.buffer(og[i].body_len);
+         System.arraycopy(og[i].body_base, og[i].body,
+                          oy.data, index, og[i].body_len);
+         oy.wrote(og[i].body_len);
+       }
+
+       oy.pageout(temp);
+       os_de.pagein(temp);
+       oy.pageout(temp);
+       os_de.pagein(temp);
+       oy.pageout(temp);
+       os_de.pagein(temp);
+       oy.pageout(temp);
+        // skip
+       oy.pageout(temp);
+       os_de.pagein(temp);
+
+        // do we get the expected results/packets?
+      
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(0,0,0);
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(100,1,-1);
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(4079,2,3000);
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(2956,3,4000);
+       if(os_de.packetout(test)!=-1){
+         System.err.println("Error: loss of page did not return error");
+         System.exit(1);
+       }
+       if(os_de.packetout(test)!=1)error();
+       test.checkpacket(300,13,14000);
+       System.err.println("ok.");
+      }
+
+      // the rest only test sync
+      {
+       Page og_de=new Page();
+        // Test fractional page inputs: incomplete capture
+       System.err.print("Testing sync on partial inputs... ");
+       oy.reset();
+       int index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header, 
+                        oy.data, index, 3);
+       oy.wrote(3);
+       if(oy.pageout(og_de)>0)error();
+      
+        // Test fractional page inputs: incomplete fixed header
+       index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header+3, 
+                        oy.data, index, 20);
+    
+       oy.wrote(20);
+       if(oy.pageout(og_de)>0)error();
+    
+        // Test fractional page inputs: incomplete header
+       index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header+23, 
+                        oy.data, index, 5);
+       oy.wrote(5);
+       if(oy.pageout(og_de)>0)error();
+    
+        // Test fractional page inputs: incomplete body
+       index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header+28, 
+                        oy.data, index, og[1].header_len-28);
+       oy.wrote(og[1].header_len-28);
+       if(oy.pageout(og_de)>0)error();
+    
+       index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body,
+                        oy.data, index, 1000);
+       oy.wrote(1000);
+       if(oy.pageout(og_de)>0)error();
+
+       index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body+1000,
+                        oy.data, index, og[1].body_len-1000);
+       oy.wrote(og[1].body_len-1000);
+       if(oy.pageout(og_de)<=0)error();
+       System.err.println("ok.");
+      }
+
+      // Test fractional page inputs: page + incomplete capture
+      {
+       Page og_de=new Page();
+       System.err.print("Testing sync on 1+partial inputs... ");
+       oy.reset(); 
+
+       int index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header,
+                        oy.data, index, og[1].header_len);
+       oy.wrote(og[1].header_len);
+
+       index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body,
+                        oy.data, index, og[1].body_len);
+       oy.wrote(og[1].body_len);
+
+       index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header,
+                        oy.data, index, 20);
+       oy.wrote(20);
+       if(oy.pageout(og_de)<=0)error();
+       if(oy.pageout(og_de)>0)error();
+
+       index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header+20,
+                        oy.data, index, og[1].header_len-20);
+       oy.wrote(og[1].header_len-20);
+       index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body,
+                        oy.data, index, og[1].body_len);
+            
+       oy.wrote(og[1].body_len);
+       if(oy.pageout(og_de)<=0)error();
+    
+       System.err.println("ok.");
+      }
+
+//    //    //    //    //    //    //    //    //    
+      // Test recapture: garbage + page
+      {
+       Page og_de=new Page();
+       System.err.print("Testing search for capture... ");
+       oy.reset(); 
+      
+        // 'garbage'
+       int index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body,
+                        oy.data, index, og[1].body_len);
+       oy.wrote(og[1].body_len);
+
+       index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header,
+                        oy.data, index, og[1].header_len);
+       oy.wrote(og[1].header_len);
+
+       index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body,
+                        oy.data, index, og[1].body_len);
+       oy.wrote(og[1].body_len);
+
+       index=oy.buffer(og[2].header_len);
+       System.arraycopy(og[2].header_base, og[2].header,
+                        oy.data, index, 20);
+
+       oy.wrote(20);
+       if(oy.pageout(og_de)>0)error();
+       if(oy.pageout(og_de)<=0)error();
+       if(oy.pageout(og_de)>0)error();
+
+       index=oy.buffer(og[2].header_len);
+       System.arraycopy(og[2].header_base, og[2].header+20,
+                        oy.data, index, og[2].header_len-20);
+       oy.wrote(og[2].header_len-20);
+       index=oy.buffer(og[2].body_len);
+       System.arraycopy(og[2].body_base, og[2].body,
+                        oy.data, index, og[2].body_len);
+       oy.wrote(og[2].body_len);
+       if(oy.pageout(og_de)<=0)error();
+
+       System.err.println("ok.");
+      }
+
+      // Test recapture: page + garbage + page
+      {
+       Page og_de=new Page();
+       System.err.print("Testing recapture... ");
+       oy.reset(); 
+
+       int index=oy.buffer(og[1].header_len);
+       System.arraycopy(og[1].header_base, og[1].header,
+                        oy.data, index, og[1].header_len);
+       oy.wrote(og[1].header_len);
+
+       index=oy.buffer(og[1].body_len);
+       System.arraycopy(og[1].body_base, og[1].body,
+                        oy.data, index, og[1].body_len);
+       oy.wrote(og[1].body_len);
+
+       index=oy.buffer(og[2].header_len);
+       System.arraycopy(og[2].header_base, og[2].header,
+                        oy.data, index, og[2].header_len);
+       oy.wrote(og[2].header_len);
+
+       index=oy.buffer(og[2].header_len);
+       System.arraycopy(og[2].header_base, og[2].header,
+                        oy.data, index, og[2].header_len);
+       oy.wrote(og[2].header_len);
+
+       if(oy.pageout(og_de)<=0)error();
+
+       index=oy.buffer(og[2].body_len);
+       System.arraycopy(og[2].body_base, og[2].body,
+                        oy.data, index, og[2].body_len-5);
+       oy.wrote(og[2].body_len-5);
+
+       index=oy.buffer(og[3].header_len);
+       System.arraycopy(og[3].header_base, og[3].header,
+                        oy.data, index, og[3].header_len);
+       oy.wrote(og[3].header_len);
+
+       index=oy.buffer(og[3].body_len);
+       System.arraycopy(og[3].body_base, og[3].body,
+                        oy.data, index, og[3].body_len);
+       oy.wrote(og[3].body_len);
+
+       if(oy.pageout(og_de)>0)error();
+       if(oy.pageout(og_de)<=0)error();
+
+       System.err.println("ok.");
+      }
+    }    
+    //return(0);
+  }
+  */
+}
diff --git a/jorbis/src/com/jcraft/jogg/StreamState.java b/jorbis/src/com/jcraft/jogg/StreamState.java
new file mode 100644 (file)
index 0000000..2f34b37
--- /dev/null
@@ -0,0 +1,657 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+public class StreamState{
+  byte[] body_data;    /* bytes from packet bodies */
+  int body_storage;    /* storage elements allocated */
+  int body_fill;       /* elements stored; fill mark */
+private  int body_returned;   /* elements of fill returned */
+
+
+  int[] lacing_vals;    /* The values that will go to the segment table */
+  long[] granule_vals;  /* pcm_pos values for headers. Not compact
+                          this way, but it is simple coupled to the
+                          lacing fifo */
+  int lacing_storage;
+  int lacing_fill;
+  int lacing_packet;
+  int lacing_returned;
+
+  byte[] header=new byte[282];      /* working space for header encode */
+  int header_fill;
+
+  public int e_o_s;   /* set when we have buffered the last packet in the
+                        logical bitstream */
+  int b_o_s;          /* set after we've written the initial page
+                        of a logical bitstream */
+  int serialno;
+  int pageno;
+  long packetno;      /* sequence number for decode; the framing
+                         knows where there's a hole in the data,
+                         but we need coupling so that the codec
+                         (which is in a seperate abstraction
+                         layer) also knows about the gap */
+  long granulepos;
+
+  public StreamState(){
+    init();
+  }
+
+  StreamState(int serialno){
+    this();
+    init(serialno);
+  }
+  void init(){
+    body_storage=16*1024;
+    body_data=new byte[body_storage];
+    lacing_storage=1024;
+    lacing_vals=new int[lacing_storage];
+    granule_vals=new long[lacing_storage];
+  }
+  public void init(int serialno){
+    if(body_data==null){ init(); }
+    else{
+      for(int i=0; i<body_data.length; i++) body_data[i]=0;
+      for(int i=0; i<lacing_vals.length; i++) lacing_vals[i]=0;
+      for(int i=0; i<granule_vals.length; i++) granule_vals[i]=0;
+    }
+    this.serialno=serialno;
+  }
+  public void clear(){
+    body_data=null;
+    lacing_vals=null;
+    granule_vals=null;
+    //memset(os,0,sizeof(ogg_stream_state));    
+  }
+  void destroy(){
+    clear();
+  }
+  void body_expand(int needed){
+    if(body_storage<=body_fill+needed){
+      body_storage+=(needed+1024);
+      byte[] foo=new byte[body_storage];
+      System.arraycopy(body_data, 0, foo, 0, body_data.length);
+      body_data=foo;
+//System.out.println("expand: body_fill="+body_fill+", body_storage="+body_data.length);
+    }
+  }
+  void lacing_expand(int needed){
+    if(lacing_storage<=lacing_fill+needed){
+      lacing_storage+=(needed+32);
+      int[] foo=new int[lacing_storage];
+      System.arraycopy(lacing_vals, 0, foo, 0, lacing_vals.length);
+      lacing_vals=foo;
+
+      long[] bar=new long[lacing_storage];
+      System.arraycopy(granule_vals, 0, bar, 0, granule_vals.length);
+      granule_vals=bar;
+    }
+  }
+
+  /* submit data to the internal buffer of the framing engine */
+  public int packetin(Packet op){
+    int lacing_val=op.bytes/255+1;
+
+    if(body_returned!=0){
+      /* advance packet data according to the body_returned pointer. We
+         had to keep it around to return a pointer into the buffer last
+         call */
+    
+      body_fill-=body_returned;
+      if(body_fill!=0){
+//        memmove(os->body_data,os->body_data+os->body_returned,
+//             os->body_fill*sizeof(char));
+        System.arraycopy(body_data, body_returned, body_data, 0, body_fill);
+      }
+      body_returned=0;
+    }
+
+    /* make sure we have the buffer storage */
+    body_expand(op.bytes);
+    lacing_expand(lacing_val);
+
+  /* Copy in the submitted packet.  Yes, the copy is a waste; this is
+     the liability of overly clean abstraction for the time being.  It
+     will actually be fairly easy to eliminate the extra copy in the
+     future */
+
+    System.arraycopy(op.packet_base, op.packet, body_data, body_fill, op.bytes);
+    body_fill+=op.bytes;
+//System.out.println("add: "+body_fill);
+
+  /* Store lacing vals for this packet */
+    int j;
+    for(j=0;j<lacing_val-1;j++){
+      lacing_vals[lacing_fill+j]=255;
+      granule_vals[lacing_fill+j]=granulepos;
+    }
+    lacing_vals[lacing_fill+j]=(op.bytes)%255;
+    granulepos=granule_vals[lacing_fill+j]=op.granulepos;
+
+  /* flag the first segment as the beginning of the packet */
+    lacing_vals[lacing_fill]|= 0x100;
+
+    lacing_fill+=lacing_val;
+
+  /* for the sake of completeness */
+    packetno++;
+
+    if(op.e_o_s!=0)e_o_s=1;
+    return(0);
+  }
+
+  public int packetout(Packet op){
+
+  /* The last part of decode. We have the stream broken into packet
+     segments.  Now we need to group them into packets (or return the
+     out of sync markers) */
+
+    int ptr=lacing_returned;
+
+    if(lacing_packet<=ptr){
+      return(0);
+    }
+
+    if((lacing_vals[ptr]&0x400)!=0){
+    /* We lost sync here; let the app know */
+      lacing_returned++;
+
+    /* we need to tell the codec there's a gap; it might need to
+       handle previous packet dependencies. */
+      packetno++;
+      return(-1);
+    }
+
+  /* Gather the whole packet. We'll have no holes or a partial packet */
+    {
+      int size=lacing_vals[ptr]&0xff;
+      int bytes=0;
+
+      op.packet_base=body_data;
+      op.packet=body_returned;
+      op.e_o_s=lacing_vals[ptr]&0x200; /* last packet of the stream? */
+      op.b_o_s=lacing_vals[ptr]&0x100; /* first packet of the stream? */
+      bytes+=size;
+
+      while(size==255){
+       int val=lacing_vals[++ptr];
+       size=val&0xff;
+       if((val&0x200)!=0)op.e_o_s=0x200;
+       bytes+=size;
+      }
+
+      op.packetno=packetno;
+      op.granulepos=granule_vals[ptr];
+      op.bytes=bytes;
+
+//System.out.println(this+" # body_returned="+body_returned);
+      body_returned+=bytes;
+//System.out.println(this+"## body_returned="+body_returned);
+
+      lacing_returned=ptr+1;
+    }
+    packetno++;
+    return(1);
+  }
+
+
+  // add the incoming page to the stream state; we decompose the page
+  // into packet segments here as well.
+
+  public int pagein(Page og){
+    byte[] header_base=og.header_base;
+    int header=og.header;
+    byte[] body_base=og.body_base;
+    int body=og.body;
+    int bodysize=og.body_len;
+    int segptr=0;
+
+    int version=og.version();
+    int continued=og.continued();
+    int bos=og.bos();
+    int eos=og.eos();
+    long granulepos=og.granulepos();
+    int _serialno=og.serialno();
+    int _pageno=og.pageno();
+    int segments=header_base[header+26]&0xff;
+
+    // clean up 'returned data'
+    {
+      int lr=lacing_returned;
+      int br=body_returned;
+
+      // body data
+
+//System.out.println("br="+br+", body_fill="+body_fill);
+
+      if(br!=0){
+        body_fill-=br;
+        if(body_fill!=0){
+         System.arraycopy(body_data, br, body_data, 0, body_fill);
+       }
+       body_returned=0;
+      }
+
+//System.out.println("?? br="+br+", body_fill="+body_fill+" body_returned="+body_returned);
+
+      if(lr!=0){
+        // segment table
+       if((lacing_fill-lr)!=0){
+         System.arraycopy(lacing_vals, lr, lacing_vals, 0, lacing_fill-lr);
+         System.arraycopy(granule_vals, lr, granule_vals, 0, lacing_fill-lr);
+       }
+       lacing_fill-=lr;
+       lacing_packet-=lr;
+       lacing_returned=0;
+      }
+    }
+
+    // check the serial number
+    if(_serialno!=serialno)return(-1);
+    if(version>0)return(-1);
+
+    lacing_expand(segments+1);
+
+    // are we in sequence?
+    if(_pageno!=pageno){
+      int i;
+
+      // unroll previous partial packet (if any)
+      for(i=lacing_packet;i<lacing_fill;i++){
+       body_fill-=lacing_vals[i]&0xff;
+//System.out.println("??");
+      }
+      lacing_fill=lacing_packet;
+
+      // make a note of dropped data in segment table
+      if(pageno!=-1){
+       lacing_vals[lacing_fill++]=0x400;
+       lacing_packet++;
+      }
+
+      // are we a 'continued packet' page?  If so, we'll need to skip
+      // some segments
+      if(continued!=0){
+       bos=0;
+       for(;segptr<segments;segptr++){
+         int val=(header_base[header+27+segptr]&0xff);
+         body+=val;
+         bodysize-=val;
+         if(val<255){
+           segptr++;
+           break;
+         }
+       }
+      }
+    }
+
+//System.out.println("bodysize="+bodysize);
+
+    if(bodysize!=0){
+      body_expand(bodysize);
+      System.arraycopy(body_base, body, body_data, body_fill, bodysize);
+      body_fill+=bodysize;
+    }
+
+//System.out.println("bodyfill="+body_fill);
+
+    {
+      int saved=-1;
+      while(segptr<segments){
+       int val=(header_base[header+27+segptr]&0xff);
+       lacing_vals[lacing_fill]=val;
+       granule_vals[lacing_fill]=-1;
+      
+       if(bos!=0){
+         lacing_vals[lacing_fill]|=0x100;
+         bos=0;
+       }
+      
+       if(val<255)saved=lacing_fill;
+      
+       lacing_fill++;
+       segptr++;
+      
+       if(val<255)lacing_packet=lacing_fill;
+      }
+  
+    /* set the granulepos on the last pcmval of the last full packet */
+      if(saved!=-1){
+       granule_vals[saved]=granulepos;
+      }
+    }
+
+    if(eos!=0){
+      e_o_s=1;
+      if(lacing_fill>0)
+       lacing_vals[lacing_fill-1]|=0x200;
+    }
+
+    pageno=_pageno+1;
+    return(0);
+  }
+
+
+/* This will flush remaining packets into a page (returning nonzero),
+   even if there is not enough data to trigger a flush normally
+   (undersized page). If there are no packets or partial packets to
+   flush, ogg_stream_flush returns 0.  Note that ogg_stream_flush will
+   try to flush a normal sized page like ogg_stream_pageout; a call to
+   ogg_stream_flush does not gurantee that all packets have flushed.
+   Only a return value of 0 from ogg_stream_flush indicates all packet
+   data is flushed into pages.
+
+   ogg_stream_page will flush the last page in a stream even if it's
+   undersized; you almost certainly want to use ogg_stream_pageout
+   (and *not* ogg_stream_flush) unless you need to flush an undersized
+   page in the middle of a stream for some reason. */
+
+  public int flush(Page og){
+
+//System.out.println(this+" ---body_returned: "+body_returned);
+
+    int i;
+    int vals=0;
+    int maxvals=(lacing_fill>255?255:lacing_fill);
+    int bytes=0;
+    int acc=0;
+    long granule_pos=granule_vals[0];
+
+    if(maxvals==0)return(0);
+  
+    /* construct a page */
+    /* decide how many segments to include */
+  
+    /* If this is the initial header case, the first page must only include
+       the initial header packet */
+    if(b_o_s==0){  /* 'initial header page' case */
+      granule_pos=0;
+      for(vals=0;vals<maxvals;vals++){
+        if((lacing_vals[vals]&0x0ff)<255){
+         vals++;
+         break;
+        }
+      }
+    }
+    else{
+      for(vals=0;vals<maxvals;vals++){
+        if(acc>4096)break;
+        acc+=(lacing_vals[vals]&0x0ff);
+        granule_pos=granule_vals[vals];
+      }
+    }
+
+    /* construct the header in temp storage */
+    System.arraycopy("OggS".getBytes(), 0, header, 0, 4);
+  
+    /* stream structure version */
+    header[4]=0x00;
+
+    /* continued packet flag? */
+    header[5]=0x00;
+    if((lacing_vals[0]&0x100)==0)header[5]|=0x01;
+    /* first page flag? */
+    if(b_o_s==0) header[5]|=0x02;
+    /* last page flag? */
+    if(e_o_s!=0 && lacing_fill==vals) header[5]|=0x04;
+    b_o_s=1;
+
+    /* 64 bits of PCM position */
+    for(i=6;i<14;i++){
+      header[i]=(byte)granule_pos;
+      granule_pos>>>=8;
+    }
+
+    /* 32 bits of stream serial number */
+    {
+      int _serialno=serialno;
+      for(i=14;i<18;i++){
+        header[i]=(byte)_serialno;
+        _serialno>>>=8;
+      }
+    }
+
+    /* 32 bits of page counter (we have both counter and page header
+       because this val can roll over) */
+    if(pageno==-1)pageno=0;       /* because someone called
+                                    stream_reset; this would be a
+                                    strange thing to do in an
+                                    encode stream, but it has
+                                    plausible uses */
+    {
+      int _pageno=pageno++;
+      for(i=18;i<22;i++){
+        header[i]=(byte)_pageno;
+        _pageno>>>=8;
+      }
+    }
+  
+    /* zero for computation; filled in later */
+    header[22]=0;
+    header[23]=0;
+    header[24]=0;
+    header[25]=0;
+  
+    /* segment table */
+    header[26]=(byte)vals;
+    for(i=0;i<vals;i++){
+      header[i+27]=(byte)lacing_vals[i];
+      bytes+=(header[i+27]&0xff);
+    }
+  
+    /* set pointers in the ogg_page struct */
+    og.header_base=header;
+    og.header=0;
+    og.header_len=header_fill=vals+27;
+    og.body_base=body_data;
+    og.body=body_returned;
+    og.body_len=bytes;
+
+    /* advance the lacing data and set the body_returned pointer */
+  
+//System.out.println("###body_returned: "+body_returned);
+
+    lacing_fill-=vals;
+    System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill*4);
+    System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill*8);
+    body_returned+=bytes;
+
+//System.out.println("####body_returned: "+body_returned);
+  
+    /* calculate the checksum */
+  
+    og.checksum();
+
+    /* done */
+    return(1);
+  }
+
+
+/* This constructs pages from buffered packet segments.  The pointers
+returned are to static buffers; do not free. The returned buffers are
+good only until the next call (using the same ogg_stream_state) */
+  public int pageout(Page og){
+//    if(body_returned!=0){
+//    /* advance packet data according to the body_returned pointer. We
+//       had to keep it around to return a pointer into the buffer last
+//       call */
+//
+//      body_fill-=body_returned;
+//      if(body_fill!=0){ // overlap?
+//     System.arraycopy(body_data, body_returned, body_data, 0, body_fill);
+//      }
+//      body_returned=0;
+//    }
+//
+//System.out.println("pageout: e_o_s="+e_o_s+" lacing_fill="+lacing_fill+" body_fill="+body_fill+", lacing_fill="+lacing_fill+" b_o_s="+b_o_s);
+//
+//    if((e_o_s!=0&&lacing_fill!=0) ||  /* 'were done, now flush' case */
+//       body_fill > 4096 ||          /* 'page nominal size' case */
+//       lacing_fill>=255 ||          /* 'segment table full' case */
+//       (lacing_fill!=0&&b_o_s==0)){  /* 'initial header page' case */
+//      int vals=0,bytes=0;
+//      int maxvals=(lacing_fill>255?255:lacing_fill);
+//      long acc=0;
+//      long pcm_pos=granule_vals[0];
+//
+//    /* construct a page */
+//    /* decide how many segments to include */
+//
+//    /* If this is the initial header case, the first page must only include
+//       the initial header packet */
+//      if(b_o_s==0){  /* 'initial header page' case */
+//        pcm_pos=0;
+//        for(vals=0;vals<maxvals;vals++){
+//       if((lacing_vals[vals]&0x0ff)<255){
+//         vals++;
+//         break;
+//       }
+//        }
+//      }
+//      else{
+//        for(vals=0;vals<maxvals;vals++){
+//       if(acc>4096)break;
+//       acc+=lacing_vals[vals]&0x0ff;
+//       pcm_pos=granule_vals[vals];
+//        }
+//      }
+//
+//    /* construct the header in temp storage */
+//      System.arraycopy("OggS".getBytes(), 0, header, 0, 4);
+//
+//    /* stream structure version */
+//      header[4]=0x00;
+//    
+//    /* continued packet flag? */
+//      header[5]=0x00;
+//      if((lacing_vals[0]&0x100)==0)header[5]|=0x01;
+//    /* first page flag? */
+//      if(b_o_s==0)header[5]|=0x02;
+//    /* last page flag? */
+//      if(e_o_s!=0 && lacing_fill==vals)header[5]|=0x04;
+//      b_o_s=1;
+//
+//    /* 64 bits of PCM position */
+//      for(int i=6;i<14;i++){
+//        header[i]=(byte)pcm_pos;
+//        pcm_pos>>>=8;
+//      }
+//
+//    /* 32 bits of stream serial number */
+//      {
+//        int serialn=serialno;
+//        for(int i=14;i<18;i++){
+//       header[i]=(byte)serialn;
+//       serialn>>>=8;
+//        }
+//      }
+//
+//
+///* 32 bits of page counter (we have both counter and page header
+//       because this val can roll over) */
+//      if(pageno==-1)pageno=0; /* because someone called
+//                                       stream_reset; this would be a
+//                                       strange thing to do in an
+//                                       encode stream, but it has
+//                                       plausible uses */
+//      {
+//        int pagen=pageno++;
+//        for(int i=18;i<22;i++){
+//       header[i]=(byte)pagen;
+//       pagen>>>=8;
+//        }
+//      }
+//
+//    /* zero for computation; filled in later */
+//      header[22]=0;
+//      header[23]=0;
+//      header[24]=0;
+//      header[25]=0;
+//
+//    /* segment table */
+//      header[26]=(byte)vals;
+//      for(int i=0;i<vals;i++){
+//        header[i+27]=(byte)lacing_vals[i];
+//        bytes+=header[i+27]&0xff;
+////      bytes+=header[i+27]=(lacing_vals[i]&0xff);
+//      }
+//      
+//    /* advance the lacing data and set the body_returned pointer */
+//
+//      lacing_fill-=vals;
+//      System.arraycopy(lacing_vals, vals, lacing_vals, 0, lacing_fill);
+//      System.arraycopy(granule_vals, vals, granule_vals, 0, lacing_fill);
+//      body_returned=bytes;
+//
+//    /* set pointers in the ogg_page struct */
+//      og.header_base=header;
+//      og.header=0;
+//      og.header_len=header_fill=vals+27;
+//
+//      og.body_base=body_data;
+//      og.body=0;
+//      og.body_len=bytes;
+//
+//    /* calculate the checksum */
+//
+//      og.checksum();
+//      return(1);
+//    }
+//    /* not enough data to construct a page and not end of stream */
+//    return(0);
+//System.out.println("pageout: "+body_returned);
+    if((e_o_s!=0&&lacing_fill!=0) ||  /* 'were done, now flush' case */
+        body_fill-body_returned> 4096 ||     /* 'page nominal size' case */
+        lacing_fill>=255 ||          /* 'segment table full' case */
+        (lacing_fill!=0&&b_o_s==0)){  /* 'initial header page' case */
+      return flush(og);
+    }
+    return 0;
+  }
+
+  public int eof(){
+    return e_o_s;
+  }
+
+  public int reset(){
+    body_fill=0;
+    body_returned=0;
+
+    lacing_fill=0;
+    lacing_packet=0;
+    lacing_returned=0;
+
+    header_fill=0;
+
+    e_o_s=0;
+    b_o_s=0;
+    pageno=-1;
+    packetno=0;
+    granulepos=0;
+    return(0);
+  }
+}
diff --git a/jorbis/src/com/jcraft/jogg/SyncState.java b/jorbis/src/com/jcraft/jogg/SyncState.java
new file mode 100644 (file)
index 0000000..b3705e5
--- /dev/null
@@ -0,0 +1,275 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jogg;
+
+// DECODING PRIMITIVES: packet streaming layer
+
+// This has two layers to place more of the multi-serialno and paging
+// control in the application's hands.  First, we expose a data buffer
+// using ogg_decode_buffer().  The app either copies into the
+// buffer, or passes it directly to read(), etc.  We then call
+// ogg_decode_wrote() to tell how many bytes we just added.
+//
+// Pages are returned (pointers into the buffer in ogg_sync_state)
+// by ogg_decode_stream().  The page is then submitted to
+// ogg_decode_page() along with the appropriate
+// ogg_stream_state* (ie, matching serialno).  We then get raw
+// packets out calling ogg_stream_packet() with a
+// ogg_stream_state.  See the 'frame-prog.txt' docs for details and
+// example code.
+
+public class SyncState{
+
+  public byte[] data;
+  int storage;
+  int fill;
+  int returned;
+
+  int unsynced;
+  int headerbytes;
+  int bodybytes;
+
+  public int clear(){
+    data=null;
+    return(0);
+  }
+
+// !!!!!!!!!!!!
+//  byte[] buffer(int size){
+  public int buffer(int size){
+    // first, clear out any space that has been previously returned
+    if(returned!=0){
+      fill-=returned;
+      if(fill>0){
+       System.arraycopy(data, returned, data, 0, fill);
+      }
+      returned=0;
+    }
+
+    if(size>storage-fill){
+      // We need to extend the internal buffer
+      int newsize=size+fill+4096; // an extra page to be nice
+      if(data!=null){
+       byte[] foo=new byte[newsize];
+       System.arraycopy(data, 0, foo, 0, data.length);
+       data=foo;
+      }
+      else{
+       data=new byte[newsize];
+      }
+      storage=newsize;
+    }
+
+    // expose a segment at least as large as requested at the fill mark
+//    return((char *)oy->data+oy->fill);
+//    return(data);
+    return(fill);
+  }
+
+  public int wrote(int bytes){
+    if(fill+bytes>storage)return(-1);
+    fill+=bytes;
+    return(0);
+  }
+
+// sync the stream.  This is meant to be useful for finding page
+// boundaries.
+//
+// return values for this:
+// -n) skipped n bytes
+//  0) page not ready; more data (no bytes skipped)
+//  n) page synced at current location; page length n bytes
+  private Page pageseek=new Page();
+  private  byte[] chksum=new byte[4];
+  public int pageseek(Page og){
+    int page=returned;
+    int next;
+    int bytes=fill-returned;
+  
+    if(headerbytes==0){
+      int _headerbytes,i;
+      if(bytes<27)return(0); // not enough for a header
+    
+    /* verify capture pattern */
+//!!!!!!!!!!!
+      if(data[page]!='O' ||
+        data[page+1]!='g' ||
+        data[page+2]!='g' ||
+        data[page+3]!='S'){
+        headerbytes=0;
+        bodybytes=0;
+  
+        // search for possible capture
+        next=0;
+        for(int ii=0; ii<bytes-1; ii++){
+          if(data[page+1+ii]=='O'){next=page+1+ii; break;}
+        }
+    //next=memchr(page+1,'O',bytes-1);
+        if(next==0) next=fill;
+
+        returned=next;
+        return(-(next-page));
+      }
+      _headerbytes=(data[page+26]&0xff)+27;
+      if(bytes<_headerbytes)return(0); // not enough for header + seg table
+    
+      // count up body length in the segment table
+    
+      for(i=0;i<(data[page+26]&0xff);i++){
+        bodybytes+=(data[page+27+i]&0xff);
+      }
+      headerbytes=_headerbytes;
+    }
+  
+    if(bodybytes+headerbytes>bytes)return(0);
+  
+    // The whole test page is buffered.  Verify the checksum
+    synchronized(chksum){
+      // Grab the checksum bytes, set the header field to zero
+    
+      System.arraycopy(data, page+22, chksum, 0, 4);
+      data[page+22]=0;
+      data[page+23]=0;
+      data[page+24]=0;
+      data[page+25]=0;
+    
+      // set up a temp page struct and recompute the checksum
+      Page log=pageseek;
+      log.header_base=data;
+      log.header=page;
+      log.header_len=headerbytes;
+
+      log.body_base=data;
+      log.body=page+headerbytes;
+      log.body_len=bodybytes;
+      log.checksum();
+
+      // Compare
+      if(chksum[0]!=data[page+22] ||
+         chksum[1]!=data[page+23] ||
+         chksum[2]!=data[page+24] ||
+         chksum[3]!=data[page+25]){
+        // D'oh.  Mismatch! Corrupt page (or miscapture and not a page at all)
+        // replace the computed checksum with the one actually read in
+        System.arraycopy(chksum, 0, data, page+22, 4);
+        // Bad checksum. Lose sync */
+
+        headerbytes=0;
+        bodybytes=0;
+        // search for possible capture
+        next=0;
+        for(int ii=0; ii<bytes-1; ii++){
+          if(data[page+1+ii]=='O'){next=page+1+ii; break;}
+        }
+        //next=memchr(page+1,'O',bytes-1);
+        if(next==0) next=fill;
+        returned=next;
+        return(-(next-page));
+      }
+    }
+  
+    // yes, have a whole page all ready to go
+    {
+      page=returned;
+
+      if(og!=null){
+        og.header_base=data;
+        og.header=page;
+        og.header_len=headerbytes;
+       og.body_base=data;
+        og.body=page+headerbytes;
+        og.body_len=bodybytes;
+      }
+
+      unsynced=0;
+      returned+=(bytes=headerbytes+bodybytes);
+      headerbytes=0;
+      bodybytes=0;
+      return(bytes);
+    }
+//  headerbytes=0;
+//  bodybytes=0;
+//  next=0;
+//  for(int ii=0; ii<bytes-1; ii++){
+//    if(data[page+1+ii]=='O'){next=page+1+ii;}
+//  }
+//  //next=memchr(page+1,'O',bytes-1);
+//  if(next==0) next=fill;
+//  returned=next;
+//  return(-(next-page));
+  }
+
+
+// sync the stream and get a page.  Keep trying until we find a page.
+// Supress 'sync errors' after reporting the first.
+//
+// return values:
+//  -1) recapture (hole in data)
+//   0) need more data
+//   1) page returned
+//
+// Returns pointers into buffered data; invalidated by next call to
+// _stream, _clear, _init, or _buffer
+
+  public int pageout(Page og){
+    // all we need to do is verify a page at the head of the stream
+    // buffer.  If it doesn't verify, we look for the next potential
+    // frame
+
+    while(true){
+      int ret=pageseek(og);
+      if(ret>0){
+        // have a page
+        return(1);
+      }
+      if(ret==0){
+        // need more data
+        return(0);
+      }
+    
+      // head did not start a synced page... skipped some bytes
+      if(unsynced==0){
+        unsynced=1;
+        return(-1);
+      }
+      // loop. keep looking
+    }
+  }
+
+// clear things to an initial state.  Good to call, eg, before seeking
+  public int reset(){
+    fill=0;
+    returned=0;
+    unsynced=0;
+    headerbytes=0;
+    bodybytes=0;
+    return(0);
+  }
+  public void init(){}
+
+  public int getDataOffset(){ return returned; }    
+  public int getBufferOffset(){ return fill;  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/AllocChain.java b/jorbis/src/com/jcraft/jorbis/AllocChain.java
new file mode 100644 (file)
index 0000000..b3492d5
--- /dev/null
@@ -0,0 +1,31 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+class AllocChain{
+  Object ptr;
+  AllocChain next;
+};
diff --git a/jorbis/src/com/jcraft/jorbis/Block.java b/jorbis/src/com/jcraft/jorbis/Block.java
new file mode 100644 (file)
index 0000000..8fd15f7
--- /dev/null
@@ -0,0 +1,188 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+public class Block{
+  ///necessary stream state for linking to the framing abstraction
+  float[][] pcm=new float[0][]; // this is a pointer into local storage
+  Buffer opb=new Buffer();
+  
+  int lW;
+  int W;
+  int nW;
+  int pcmend;
+  int mode;
+
+  int eofflag;
+  long granulepos;
+  long sequence;
+  DspState vd; // For read-only access of configuration
+
+  // local storage to avoid remallocing; it's up to the mapping to
+  // structure it
+//byte[] localstore;
+//int  localtop;
+//int  localalloc;
+//int  totaluse;
+//AllocChain reap;
+
+  // bitmetrics for the frame
+  int glue_bits;
+  int time_bits;
+  int floor_bits;
+  int res_bits;
+
+  public Block(DspState vd){
+    this.vd=vd;
+//  localalloc=0;
+//  localstore=null;
+    if(vd.analysisp!=0){
+      opb.writeinit();
+    }
+  }
+
+  public void init(DspState vd){
+    this.vd=vd;
+  }
+
+//  int alloc(int bytes){
+//    bytes=(bytes+(8-1))&(~(8-1));
+//    if(bytes+localtop>localalloc){
+//      if(localstore!=null){
+//     AllocChain link=new AllocChain();
+//     totaluse+=localtop;
+//     link.next=reap;
+//     link.ptr=localstore;
+//     reap=link;
+//      }
+//      // highly conservative
+//      localalloc=bytes;
+//      localstore=new byte[localalloc];
+//      localtop=0;
+//    }
+//    {
+//      int foo=localtop;
+//      //void *ret=(void *)(((char *)vb->localstore)+vb->localtop);
+//      localtop+=bytes;
+//      return foo;
+//    }
+//  }
+
+  // reap the chain, pull the ripcord
+//  void ripcord(){
+//    // reap the chain
+//    while(reap!=null){
+//      AllocChain next=reap.next;
+//      //free(reap->ptr);
+//      reap.ptr=null;
+//      //memset(reap,0,sizeof(struct alloc_chain));
+//      //free(reap);
+//      reap=next;
+//    }
+//    // consolidate storage
+//    if(totaluse!=0){
+//      //vb->localstore=realloc(vb->localstore,vb->totaluse+vb->localalloc);
+//      byte[] foo=new byte[totaluse+localalloc];
+//      System.arraycopy(localstore, 0, foo, 0, localstore.length);
+//      localstore=foo;
+//      localalloc+=totaluse;
+//      totaluse=0;
+//    }
+//    // pull the ripcord
+//    localtop=0;
+//    reap=null;
+//  }
+
+  public int clear(){
+    if(vd!=null){
+      if(vd.analysisp!=0){
+       opb.writeclear();
+      }
+    }
+    //ripcord();
+    //if(localstore!=null)
+    //  localstore=null;
+    //memset(vb,0,sizeof(vorbis_block));
+    return(0);
+  }
+
+  public int synthesis(Packet op){
+    Info vi=vd.vi;
+    // first things first.  Make sure decode is ready
+    // ripcord();
+    opb.readinit(op.packet_base, op.packet, op.bytes);
+
+    // Check the packet type
+    if(opb.read(1)!=0){
+      // Oops.  This is not an audio data packet
+      return(-1);
+    }
+
+    // read our mode and pre/post windowsize
+    int _mode=opb.read(vd.modebits);
+    if(_mode==-1)return(-1);
+  
+    mode=_mode;
+    W=vi.mode_param[mode].blockflag;
+    if(W!=0){
+      lW=opb.read(1);
+      nW=opb.read(1);
+      if(nW==-1) return(-1);
+    }
+    else{
+      lW=0;
+      nW=0;
+    }
+  
+    // more setup
+    granulepos=op.granulepos;
+    sequence=op.packetno-3; // first block is third packet
+    eofflag=op.e_o_s;
+
+    // alloc pcm passback storage
+    pcmend=vi.blocksizes[W];
+    //pcm=alloc(vi.channels);
+    if(pcm.length<vi.channels){
+      pcm=new float[vi.channels][];
+    }
+    for(int i=0;i<vi.channels;i++){
+      if(pcm[i]==null || pcm[i].length<pcmend){
+        pcm[i]=new float[pcmend];
+        //pcm[i]=alloc(pcmend);
+      }
+      else{
+        for(int j=0;j<pcmend;j++){ pcm[i][j]=0; }
+      }
+    }
+
+    // unpack_header enforces range checking
+    int type=vi.map_type[vi.mode_param[mode].mapping];
+    return(FuncMapping.mapping_P[type].inverse(this, vd.mode[mode]));
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/ChainingExample.java b/jorbis/src/com/jcraft/jorbis/ChainingExample.java
new file mode 100644 (file)
index 0000000..82592f2
--- /dev/null
@@ -0,0 +1,61 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+class ChainingExample{
+  public static void main(String[] arg){
+    VorbisFile ov=null;
+
+    try{
+      ov=new VorbisFile(System.in, null, -1);
+    }
+    catch(Exception e){
+      System.err.println(e);
+      return;
+    }
+
+    if(ov.seekable()){
+      System.out.println("Input bitstream contained "+ov.streams()+" logical bitstream section(s).");
+      System.out.println("Total bitstream playing time: "+ov.time_total(-1)+" seconds\n");
+    }
+    else{
+      System.out.println("Standard input was not seekable.");
+      System.out.println("First logical bitstream information:\n");
+    }
+
+    for(int i=0;i<ov.streams();i++){
+      Info vi=ov.getInfo(i);
+      System.out.println("\tlogical bitstream section "+(i+1)+" information:");
+      System.out.println("\t\t"+vi.rate+"Hz "+vi.channels+" channels bitrate "+
+                         (ov.bitrate(i)/1000)+"kbps serial number="+ov.serialnumber(i));
+      System.out.print("\t\tcompressed length: "+ov.raw_total(i)+" bytes ");
+      System.out.println(" play time: "+ov.time_total(i)+"s");
+      Comment vc=ov.getComment(i);
+      System.out.println(vc);
+    }
+    //clear(&ov);
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/CodeBook.java b/jorbis/src/com/jcraft/jorbis/CodeBook.java
new file mode 100644 (file)
index 0000000..9708e06
--- /dev/null
@@ -0,0 +1,742 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+class CodeBook{
+  int dim;            // codebook dimensions (elements per vector)
+  int entries;        // codebook entries
+  StaticCodeBook c=new StaticCodeBook();
+
+  float[] valuelist; // list of dim*entries actual entry values
+  int[] codelist;     // list of bitstream codewords for each entry
+  DecodeAux decode_tree;
+
+  // returns the number of bits
+  int encode(int a, Buffer b){
+    b.write(codelist[a], c.lengthlist[a]);
+    return(c.lengthlist[a]);
+  }
+
+  // One the encode side, our vector writers are each designed for a
+  // specific purpose, and the encoder is not flexible without modification:
+  // 
+  // The LSP vector coder uses a single stage nearest-match with no
+  // interleave, so no step and no error return.  This is specced by floor0
+  // and doesn't change.
+  // 
+  // Residue0 encoding interleaves, uses multiple stages, and each stage
+  // peels of a specific amount of resolution from a lattice (thus we want
+  // to match by threshhold, not nearest match).  Residue doesn't *have* to
+  // be encoded that way, but to change it, one will need to add more
+  // infrastructure on the encode side (decode side is specced and simpler)
+
+  // floor0 LSP (single stage, non interleaved, nearest match)
+  // returns entry number and *modifies a* to the quantization value
+  int errorv(float[] a){
+    int best=best(a,1);
+    for(int k=0;k<dim;k++){
+      a[k]=valuelist[best*dim+k];
+    }
+    return(best);
+  }
+
+  // returns the number of bits and *modifies a* to the quantization value
+  int encodev(int best, float[] a, Buffer b){
+    for(int k=0;k<dim;k++){
+      a[k]=valuelist[best*dim+k];
+    }
+    return(encode(best,b));
+  }
+
+  // res0 (multistage, interleave, lattice)
+  // returns the number of bits and *modifies a* to the remainder value
+  int encodevs(float[] a, Buffer b, int step,int addmul){
+    int best=besterror(a,step,addmul);
+    return(encode(best,b));
+  }
+
+  private int[] t=new int[15];  // decodevs_add is synchronized for re-using t.
+  synchronized int decodevs_add(float[]a, int offset, Buffer b, int n){
+    int step=n/dim;
+    int entry;
+    int i,j,o;
+
+    if(t.length<step){
+      t=new int[step];
+    }
+
+    for(i = 0; i < step; i++){
+      entry=decode(b);
+      if(entry==-1)return(-1);
+      t[i]=entry*dim;
+    }
+    for(i=0,o=0;i<dim;i++,o+=step){
+      for(j=0;j<step;j++){
+       a[offset+o+j]+=valuelist[t[j]+i];
+      }
+    }
+
+    return(0);
+  }
+
+  int decodev_add(float[]a, int offset, Buffer b,int n){
+    int i,j,entry;
+    int t;
+
+    if(dim>8){
+      for(i=0;i<n;){
+        entry = decode(b);
+        if(entry==-1)return(-1);
+       t=entry*dim;
+       for(j=0;j<dim;){
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       }
+      }
+    }
+    else{
+      for(i=0;i<n;){
+       entry=decode(b);
+       if(entry==-1)return(-1);
+       t=entry*dim;
+       j=0;
+       switch(dim){
+       case 8:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 7:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 6:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 5:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 4:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 3:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 2:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 1:
+         a[offset+(i++)]+=valuelist[t+(j++)];
+       case 0:
+       break;
+       }
+      }
+    }    
+    return(0);
+  }
+
+  int decodev_set(float[] a,int offset, Buffer b, int n){
+    int i,j,entry;
+    int t;
+
+    for(i=0;i<n;){
+      entry = decode(b);
+      if(entry==-1)return(-1);
+      t=entry*dim;
+      for(j=0;j<dim;){
+        a[offset+i++]=valuelist[t+(j++)];
+      }
+    }
+    return(0);
+  }
+
+  int decodevv_add(float[][] a, int offset,int ch, Buffer b,int n){
+    int i,j,k,entry;
+    int chptr=0;
+    //System.out.println("decodevv_add: a="+a+",b="+b+",valuelist="+valuelist);
+
+    for(i=offset/ch;i<(offset+n)/ch;){
+      entry = decode(b);
+      if(entry==-1)return(-1);
+
+      int t = entry*dim;
+      for(j=0;j<dim;j++){
+        a[chptr++][i]+=valuelist[t+j];
+        if(chptr==ch){
+          chptr=0;
+         i++;
+       }
+      }
+    }
+    return(0);
+  }
+
+
+  // Decode side is specced and easier, because we don't need to find
+  // matches using different criteria; we simply read and map.  There are
+  // two things we need to do 'depending':
+  //   
+  // We may need to support interleave.  We don't really, but it's
+  // convenient to do it here rather than rebuild the vector later.
+  //
+  // Cascades may be additive or multiplicitive; this is not inherent in
+  // the codebook, but set in the code using the codebook.  Like
+  // interleaving, it's easiest to do it here.  
+  // stage==0 -> declarative (set the value)
+  // stage==1 -> additive
+  // stage==2 -> multiplicitive
+
+  // returns the entry number or -1 on eof
+  int decode(Buffer b){
+    int ptr=0;
+    DecodeAux t=decode_tree;
+    int lok=b.look(t.tabn);
+    //System.err.println(this+" "+t+" lok="+lok+", tabn="+t.tabn);
+
+    if(lok>=0){
+      ptr=t.tab[lok];
+      b.adv(t.tabl[lok]);
+      if(ptr<=0){
+        return -ptr;
+      }
+    }
+    do{
+      switch(b.read1()){
+      case 0:
+       ptr=t.ptr0[ptr];
+       break;
+      case 1:
+       ptr=t.ptr1[ptr];
+       break;
+      case -1:
+      default:
+       return(-1);
+      }
+    }
+    while(ptr>0);
+    return(-ptr);
+  }
+
+  // returns the entry number or -1 on eof
+  int decodevs(float[] a, int index, Buffer b, int step,int addmul){
+    int entry=decode(b);
+    if(entry==-1)return(-1);
+    switch(addmul){
+    case -1:
+      for(int i=0,o=0;i<dim;i++,o+=step)
+       a[index+o]=valuelist[entry*dim+i];
+      break;
+    case 0:
+      for(int i=0,o=0;i<dim;i++,o+=step)
+       a[index+o]+=valuelist[entry*dim+i];
+      break;
+    case 1:
+      for(int i=0,o=0;i<dim;i++,o+=step)
+       a[index+o]*=valuelist[entry*dim+i];
+      break;
+    default:
+      //System.err.println("CodeBook.decodeves: addmul="+addmul); 
+    }
+    return(entry);
+  }
+
+  int best(float[] a, int step){
+    EncodeAuxNearestMatch nt=c.nearest_tree;
+    EncodeAuxThreshMatch tt=c.thresh_tree;
+    int ptr=0;
+
+    // we assume for now that a thresh tree is the only other possibility
+    if(tt!=null){
+      int index=0;
+      // find the quant val of each scalar
+      for(int k=0,o=step*(dim-1);k<dim;k++,o-=step){
+       int i;
+       // linear search the quant list for now; it's small and although
+       // with > 8 entries, it would be faster to bisect, this would be
+       // a misplaced optimization for now
+       for(i=0;i<tt.threshvals-1;i++){
+         if(a[o]<tt.quantthresh[i]){
+           break;
+         }
+       }
+       index=(index*tt.quantvals)+tt.quantmap[i];
+      }
+      // regular lattices are easy :-)
+      if(c.lengthlist[index]>0){
+       // is this unused?  If so, we'll
+       // use a decision tree after all
+       // and fall through
+       return(index);
+      }
+    }
+    if(nt!=null){
+      // optimized using the decision tree
+      while(true){
+       float c=0.f;
+       int p=nt.p[ptr];
+       int q=nt.q[ptr];
+       for(int k=0,o=0;k<dim;k++,o+=step){
+         c+=(valuelist[p+k]-valuelist[q+k])*
+            (a[o]-(valuelist[p+k]+valuelist[q+k])*.5);
+       }
+       if(c>0.){ // in A
+         ptr= -nt.ptr0[ptr];
+       }
+       else{     // in B
+         ptr= -nt.ptr1[ptr];
+       }
+       if(ptr<=0)break;
+      }
+      return(-ptr);
+    }
+
+    // brute force it!
+    {
+      int besti=-1;
+      float best=0.f;
+      int e=0;
+      for(int i=0;i<entries;i++){
+       if(c.lengthlist[i]>0){
+         float _this=dist(dim, valuelist, e, a, step);
+         if(besti==-1 || _this<best){
+           best=_this;
+           besti=i;
+         }
+       }
+       e+=dim;
+      }
+      return(besti);
+    }
+  }
+
+  // returns the entry number and *modifies a* to the remainder value
+  int besterror(float[] a, int step, int addmul){
+    int best=best(a,step);
+    switch(addmul){
+    case 0:
+      for(int i=0,o=0;i<dim;i++,o+=step)
+       a[o]-=valuelist[best*dim+i];
+      break;
+    case 1:
+      for(int i=0,o=0;i<dim;i++,o+=step){
+       float val=valuelist[best*dim+i];
+       if(val==0){
+         a[o]=0;
+       }else{
+         a[o]/=val;
+       }
+      }
+      break;
+    }
+    return(best);
+  }
+
+  void clear(){
+    // static book is not cleared; we're likely called on the lookup and
+    // the static codebook belongs to the info struct
+    //if(decode_tree!=null){
+    //  free(b->decode_tree->ptr0);
+    //  free(b->decode_tree->ptr1);
+    //  memset(b->decode_tree,0,sizeof(decode_aux));
+    //  free(b->decode_tree);
+    //}
+    //if(valuelist!=null)free(b->valuelist);
+    //if(codelist!=null)free(b->codelist);
+    //memset(b,0,sizeof(codebook));
+  }
+
+  private static float dist(int el, float[] ref, int index, float[] b, int step){
+    float acc=(float)0.;
+    for(int i=0; i<el; i++){
+      float val=(ref[index+i]-b[i*step]);
+      acc+=val*val;
+    }
+    return(acc);
+  }
+
+/*
+  int init_encode(StaticCodeBook s){
+    //memset(c,0,sizeof(codebook));
+    c=s;
+    entries=s.entries;
+    dim=s.dim;
+    codelist=make_words(s.lengthlist, s.entries);
+    valuelist=s.unquantize();
+    return(0);
+  }
+*/
+
+  int init_decode(StaticCodeBook s){
+    //memset(c,0,sizeof(codebook));
+    c=s;
+    entries=s.entries;
+    dim=s.dim;
+    valuelist=s.unquantize();
+
+    decode_tree=make_decode_tree();
+    if(decode_tree==null){
+      //goto err_out;
+      clear();
+      return(-1);
+    }
+    return(0);
+//  err_out:
+//    vorbis_book_clear(c);
+//    return(-1);
+  }
+
+  // given a list of word lengths, generate a list of codewords.  Works
+  // for length ordered or unordered, always assigns the lowest valued
+  // codewords first.  Extended to handle unused entries (length 0)
+  static int[] make_words(int[] l, int n){
+    int[] marker=new int[33];
+    int[] r=new int[n];
+    //memset(marker,0,sizeof(marker));
+
+    for(int i=0;i<n;i++){
+      int length=l[i];
+      if(length>0){
+       int entry=marker[length];
+      
+       // when we claim a node for an entry, we also claim the nodes
+       // below it (pruning off the imagined tree that may have dangled
+       // from it) as well as blocking the use of any nodes directly
+       // above for leaves
+      
+       // update ourself
+       if(length<32 && (entry>>>length)!=0){
+         // error condition; the lengths must specify an overpopulated tree
+         //free(r);
+         return(null);
+       }
+       r[i]=entry;
+    
+       // Look to see if the next shorter marker points to the node
+       // above. if so, update it and repeat.
+       {
+         for(int j=length;j>0;j--){
+           if((marker[j]&1)!=0){
+             // have to jump branches
+             if(j==1)marker[1]++;
+             else marker[j]=marker[j-1]<<1;
+             break; // invariant says next upper marker would already
+                     // have been moved if it was on the same path
+           }
+           marker[j]++;
+         }
+       }
+      
+       // prune the tree; the implicit invariant says all the longer
+       // markers were dangling from our just-taken node.  Dangle them
+       // from our *new* node.
+       for(int j=length+1;j<33;j++){
+         if((marker[j]>>>1) == entry){
+           entry=marker[j];
+           marker[j]=marker[j-1]<<1;
+         }
+         else{
+           break;
+         }
+       }    
+      }
+    }
+
+    // bitreverse the words because our bitwise packer/unpacker is LSb
+    // endian
+    for(int i=0;i<n;i++){
+      int temp=0;
+      for(int j=0;j<l[i];j++){
+       temp<<=1;
+       temp|=(r[i]>>>j)&1;
+      }
+      r[i]=temp;
+    }
+
+    return(r);
+  }
+
+  // build the decode helper tree from the codewords 
+  DecodeAux make_decode_tree(){
+    int top=0;
+    DecodeAux t=new DecodeAux();
+    int[] ptr0=t.ptr0=new int[entries*2];
+    int[] ptr1=t.ptr1=new int[entries*2];
+    int[] codelist=make_words(c.lengthlist, c.entries);
+
+    if(codelist==null)return(null);
+    t.aux=entries*2;
+
+    for(int i=0;i<entries;i++){
+      if(c.lengthlist[i]>0){
+       int ptr=0;
+       int j;
+       for(j=0;j<c.lengthlist[i]-1;j++){
+         int bit=(codelist[i]>>>j)&1;
+         if(bit==0){
+           if(ptr0[ptr]==0){
+             ptr0[ptr]=++top;
+           }
+           ptr=ptr0[ptr];
+         }
+         else{
+           if(ptr1[ptr]==0){
+             ptr1[ptr]= ++top;
+           }
+           ptr=ptr1[ptr];
+         }
+       }
+
+       if(((codelist[i]>>>j)&1)==0){ ptr0[ptr]=-i; }
+       else{ ptr1[ptr]=-i; }
+
+      }
+    }
+    //free(codelist);
+
+    t.tabn = ilog(entries)-4;
+
+    if(t.tabn<5)t.tabn=5;
+    int n = 1<<t.tabn;
+    t.tab = new int[n];
+    t.tabl = new int[n];
+    for(int i = 0; i < n; i++){
+      int p = 0;
+      int j=0;
+      for(j = 0; j < t.tabn && (p > 0 || j == 0); j++){
+        if ((i&(1<<j))!=0){
+         p = ptr1[p];
+       }
+        else{
+         p = ptr0[p];
+       }
+      }
+      t.tab[i]=p;  // -code
+      t.tabl[i]=j; // length 
+    }
+
+    return(t);
+  }
+
+  private static int ilog(int v){
+    int ret=0;
+    while(v!=0){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+/*
+  // TEST
+  // Simple enough; pack a few candidate codebooks, unpack them.  Code a
+  // number of vectors through (keeping track of the quantized values),
+  // and decode using the unpacked book.  quantized version of in should
+  // exactly equal out
+
+  //#include "vorbis/book/lsp20_0.vqh"
+  //#include "vorbis/book/lsp32_0.vqh"
+  //#include "vorbis/book/res0_1a.vqh"
+  static final int TESTSIZE=40;
+
+  static float[] test1={
+    0.105939,
+    0.215373,
+    0.429117,
+    0.587974,
+
+    0.181173,
+    0.296583,
+    0.515707,
+    0.715261,
+
+    0.162327,
+    0.263834,
+    0.342876,
+    0.406025,
+
+    0.103571,
+    0.223561,
+    0.368513,
+    0.540313,
+
+    0.136672,
+    0.395882,
+    0.587183,
+    0.652476,
+
+    0.114338,
+    0.417300,
+    0.525486,
+    0.698679,
+
+    0.147492,
+    0.324481,
+    0.643089,
+    0.757582,
+
+    0.139556,
+    0.215795,
+    0.324559,
+    0.399387,
+
+    0.120236,
+    0.267420,
+    0.446940,
+    0.608760,
+
+    0.115587,
+    0.287234,
+    0.571081,
+    0.708603,
+  };
+
+  static float[] test2={
+    0.088654,
+    0.165742,
+    0.279013,
+    0.395894,
+
+    0.110812,
+    0.218422,
+    0.283423,
+    0.371719,
+
+    0.136985,
+    0.186066,
+    0.309814,
+    0.381521,
+
+    0.123925,
+    0.211707,
+    0.314771,
+    0.433026,
+
+    0.088619,
+    0.192276,
+    0.277568,
+    0.343509,
+
+    0.068400,
+    0.132901,
+    0.223999,
+    0.302538,
+
+    0.202159,
+    0.306131,
+    0.360362,
+    0.416066,
+
+    0.072591,
+    0.178019,
+    0.304315,
+    0.376516,
+
+    0.094336,
+    0.188401,
+    0.325119,
+    0.390264,
+
+    0.091636,
+    0.223099,
+    0.282899,
+    0.375124,
+  };
+
+  static float[] test3={
+    0,1,-2,3,4,-5,6,7,8,9,
+    8,-2,7,-1,4,6,8,3,1,-9,
+    10,11,12,13,14,15,26,17,18,19,
+    30,-25,-30,-1,-5,-32,4,3,-2,0};
+
+//  static_codebook *testlist[]={&_vq_book_lsp20_0,
+//                            &_vq_book_lsp32_0,
+//                            &_vq_book_res0_1a,NULL};
+  static[][] float testvec={test1,test2,test3};
+
+  static void main(String[] arg){
+    Buffer write=new Buffer();
+    Buffer read=new Buffer();
+    int ptr=0;
+    write.writeinit();
+  
+    System.err.println("Testing codebook abstraction...:");
+
+    while(testlist[ptr]!=null){
+      CodeBook c=new CodeBook();
+      StaticCodeBook s=new StaticCodeBook();;
+      float *qv=alloca(sizeof(float)*TESTSIZE);
+      float *iv=alloca(sizeof(float)*TESTSIZE);
+      memcpy(qv,testvec[ptr],sizeof(float)*TESTSIZE);
+      memset(iv,0,sizeof(float)*TESTSIZE);
+
+      System.err.print("\tpacking/coding "+ptr+"... ");
+
+      // pack the codebook, write the testvector
+      write.reset();
+      vorbis_book_init_encode(&c,testlist[ptr]); // get it into memory
+                                                // we can write
+      vorbis_staticbook_pack(testlist[ptr],&write);
+      System.err.print("Codebook size "+write.bytes()+" bytes... ");
+      for(int i=0;i<TESTSIZE;i+=c.dim){
+       vorbis_book_encodev(&c,qv+i,&write);
+      }
+      c.clear();
+    
+      System.err.print("OK.\n");
+      System.err.print("\tunpacking/decoding "+ptr+"... ");
+
+      // transfer the write data to a read buffer and unpack/read
+      _oggpack_readinit(&read,_oggpack_buffer(&write),_oggpack_bytes(&write));
+      if(s.unpack(read)){
+       System.err.print("Error unpacking codebook.\n");
+       System.exit(1);
+      }
+      if(vorbis_book_init_decode(&c,&s)){
+       System.err.print("Error initializing codebook.\n");
+       System.exit(1);
+      }
+      for(int i=0;i<TESTSIZE;i+=c.dim){
+       if(vorbis_book_decodevs(&c,iv+i,&read,1,-1)==-1){
+         System.err.print("Error reading codebook test data (EOP).\n");
+         System.exit(1);
+       }
+      }
+      for(int i=0;i<TESTSIZE;i++){
+       if(fabs(qv[i]-iv[i])>.000001){
+         System.err.print("read ("+iv[i]+") != written ("+qv[i]+") at position ("+i+")\n");
+         System.exit(1);
+       }
+      }
+
+      System.err.print("OK\n");
+      ptr++;
+    }
+    // The above is the trivial stuff; 
+    // now try unquantizing a log scale codebook
+  }
+*/
+}
+
+class DecodeAux{
+  int[] tab;
+  int[] tabl;
+  int tabn;
+
+  int[] ptr0;
+  int[] ptr1;
+  int   aux;        // number of tree entries
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Comment.java b/jorbis/src/com/jcraft/jorbis/Comment.java
new file mode 100644 (file)
index 0000000..f83b7cb
--- /dev/null
@@ -0,0 +1,252 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+// the comments are not part of vorbis_info so that vorbis_info can be
+// static storage
+public class Comment{
+  private static byte[] _vorbis="vorbis".getBytes();
+
+  private static final int OV_EFAULT=-129;
+  private static final int OV_EIMPL=-130;
+
+  // unlimited user comment fields.  libvorbis writes 'libvorbis'
+  // whatever vendor is set to in encode
+  public byte[][] user_comments;
+  public int[] comment_lengths; 
+  public int comments;
+  public byte[] vendor;
+
+  public void init(){
+    user_comments=null;
+    comments=0;
+    vendor=null;
+  }
+
+  public void add(String comment){
+    add(comment.getBytes());
+  }
+
+  private void add(byte[] comment){
+    byte[][] foo=new byte[comments+2][];
+    if(user_comments!=null){
+      System.arraycopy(user_comments, 0, foo, 0, comments);
+    }
+    user_comments=foo;
+
+    int[] goo=new int[comments+2];
+    if(comment_lengths!=null){
+      System.arraycopy(comment_lengths, 0, goo, 0, comments);
+    }
+    comment_lengths=goo;
+
+    byte[] bar=new byte[comment.length+1];
+    System.arraycopy(comment, 0, bar, 0, comment.length);
+    user_comments[comments]=bar;
+    comment_lengths[comments]=comment.length;
+    comments++;
+    user_comments[comments]=null;
+  }
+
+  public void add_tag(String tag, String contents){
+    if(contents==null) contents="";
+    add(tag+"="+contents);
+  }
+
+/*
+  private void add_tag(byte[] tag, byte[] contents){
+    byte[] foo=new byte[tag.length+contents.length+1];
+    int j=0; 
+    for(int i=0; i<tag.length; i++){foo[j++]=tag[i];}
+    foo[j++]=(byte)'='; j++;
+    for(int i=0; i<contents.length; i++){foo[j++]=tag[i];}
+    add(foo);
+  }
+*/
+  // This is more or less the same as strncasecmp - but that doesn't exist
+  // * everywhere, and this is a fairly trivial function, so we include it
+  static boolean tagcompare(byte[] s1, byte[] s2, int n){
+    int c=0;
+    byte u1, u2;
+    while(c < n){
+      u1=s1[c]; u2=s2[c];
+      if('Z'>=u1 && u1>='A')u1=(byte)(u1-'A'+'a');
+      if('Z'>=u2 && u2>='A')u2=(byte)(u2-'A'+'a');
+      if(u1!=u2){ return false; }
+      c++;
+    }
+    return true;
+  }
+
+  public String query(String tag){
+    return query(tag, 0);
+  }
+
+  public String query(String tag, int count){
+    int foo=query(tag.getBytes(), count);
+    if(foo==-1)return null;
+    byte[] comment=user_comments[foo];
+    for(int i=0; i<comment_lengths[foo]; i++){
+      if(comment[i]=='='){
+        return new String(comment, i+1, comment_lengths[foo]-(i+1));
+      }
+    }
+    return null;
+  }
+
+  private int query(byte[] tag, int count){
+    int i=0;
+    int found = 0;
+    int fulltaglen = tag.length + 1;
+    byte[] fulltag = new byte[fulltaglen];
+    System.arraycopy(tag, 0, fulltag, 0, tag.length);
+    fulltag[tag.length]=(byte)'=';
+
+    for(i=0;i<comments;i++){
+      if(tagcompare(user_comments[i], fulltag, fulltaglen)){
+        if(count==found){
+         // We return a pointer to the data, not a copy
+          //return user_comments[i] + taglen + 1;
+          return i;
+       }
+        else{ found++; }
+      }
+    }
+    return -1;
+  }
+
+  int unpack(Buffer opb){
+    int vendorlen=opb.read(32);
+    if(vendorlen<0){
+      //goto err_out;
+      clear();
+      return(-1);
+    }
+    vendor=new byte[vendorlen+1];
+    opb.read(vendor,vendorlen);
+    comments=opb.read(32);
+    if(comments<0){
+      //goto err_out;
+      clear();
+      return(-1);
+    }
+    user_comments=new byte[comments+1][];
+    comment_lengths=new int[comments+1];
+           
+    for(int i=0;i<comments;i++){
+      int len=opb.read(32);
+      if(len<0){
+       //goto err_out;
+       clear();
+       return(-1);
+      }
+      comment_lengths[i]=len;
+      user_comments[i]=new byte[len+1];
+      opb.read(user_comments[i], len);
+    }    
+    if(opb.read(1)!=1){
+      //goto err_out; // EOP check
+      clear();
+      return(-1);
+
+    }
+    return(0);
+//  err_out:
+//    comment_clear(vc);
+//    return(-1);
+  }
+
+  int pack(Buffer opb){
+    byte[] temp="Xiphophorus libVorbis I 20000508".getBytes();
+
+    // preamble
+    opb.write(0x03,8);
+    opb.write(_vorbis);
+
+    // vendor
+    opb.write(temp.length,32);
+    opb.write(temp);
+
+    // comments
+
+    opb.write(comments,32);
+    if(comments!=0){
+      for(int i=0;i<comments;i++){
+       if(user_comments[i]!=null){
+         opb.write(comment_lengths[i],32);
+         opb.write(user_comments[i]);
+       }
+       else{
+         opb.write(0,32);
+       }
+      }
+    }
+    opb.write(1,1);
+    return(0);
+  }
+
+  public int header_out(Packet op){
+    Buffer opb=new Buffer();
+    opb.writeinit();
+
+    if(pack(opb)!=0) return OV_EIMPL;
+
+    op.packet_base = new byte[opb.bytes()];
+    op.packet=0;
+    op.bytes=opb.bytes();
+    System.arraycopy(opb.buffer(), 0, op.packet_base, 0, op.bytes);
+    op.b_o_s=0;
+    op.e_o_s=0;
+    op.granulepos=0;
+    return 0;
+  }
+  void clear(){
+    for(int i=0;i<comments;i++)
+      user_comments[i]=null;
+    user_comments=null;
+    vendor=null;
+  }
+
+  public String getVendor(){
+    return new String(vendor, 0, vendor.length-1);
+  }
+  public String getComment(int i){
+    if(comments<=i)return null;
+    return new String(user_comments[i], 0, user_comments[i].length-1);
+  }
+  public String toString(){
+    String foo="Vendor: "+new String(vendor, 0, vendor.length-1);
+    for(int i=0; i<comments; i++){
+      foo=foo+"\nComment: "+new String(user_comments[i], 0, user_comments[i].length-1);
+    }
+    foo=foo+"\n";
+    return foo;
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/DecodeExample.java b/jorbis/src/com/jcraft/jorbis/DecodeExample.java
new file mode 100644 (file)
index 0000000..f876896
--- /dev/null
@@ -0,0 +1,316 @@
+/* JOrbis
+ * Copyright (C) 2000 ymnk, JCraft,Inc.
+ *  
+ * Written by: 2000 ymnk<ymnk@jcraft.com>
+ *   
+ * Many thanks to 
+ *   Monty <monty@xiph.org> and 
+ *   The XIPHOPHORUS Company http://www.xiph.org/ .
+ * JOrbis has been based on their awesome works, Vorbis codec.
+ *   
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Library 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 Library General Public License for more details.
+ * 
+ * You should have received a copy of the GNU Library General Public
+ * License along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+package com.jcraft.jorbis;
+
+import com.jcraft.jogg.*;
+
+// Takes a vorbis bitstream from stdin and writes raw stereo PCM to
+// stdout.  Decodes simple and chained OggVorbis files from beginning
+// to end.  Vorbisfile.a is somewhat more complex than the code below.
+
+class DecodeExample{
+  static int convsize=4096*2;
+  static byte[] convbuffer=new byte[convsize]; // take 8k out of the data segment, not the stack
+
+  public static void main(String[] arg){    
+    java.io.InputStream input=System.in;
+    if(arg.length>0){
+      try{
+        input=new java.io.FileInputStream(arg[0]);
+      }
+      catch(Exception e){
+        System.err.println(e);
+      }
+    }
+
+    SyncState oy=new SyncState(); // sync and verify incoming physical bitstream
+    StreamState os=new StreamState(); // take physical pages, weld into a logical stream of packets
+    Page og=new Page(); // one Ogg bitstream page.  Vorbis packets are inside
+    Packet op=new Packet(); // one raw packet of data for decode
+  
+    Info vi=new Info();  // struct that stores all the static vorbis bitstream settings
+    Comment vc=new Comment(); // struct that stores all the bitstream user comments
+    DspState vd=new DspState(); // central working state for the packet->PCM decoder
+    Block vb=new Block(vd); // local working space for packet->PCM decode
+  
+    byte[] buffer;
+    int bytes=0;
+
+    // Decode setup
+
+    oy.init(); // Now we can read pages
+  
+    while(true){ // we repeat if the bitstream is chained
+      int eos=0;
+
+      // grab some data at the head of the stream.  We want the first page
+      // (which is guaranteed to be small and only contain the Vorbis
+      // stream initial header) We need the first page to get the stream
+      // serialno.
+
+      // submit a 4k block to libvorbis' Ogg layer
+      int index=oy.buffer(4096);
+      buffer=oy.data;
+      try{
+        bytes=input.read(buffer, index, 4096);
+      }
+      catch(Exception e){
+        System.err.println(e);
+        System.exit(-1);
+      }
+      oy.wrote(bytes);
+    
+      // Get the first page.
+      if(oy.pageout(og)!=1){
+        // have we simply run out of data?  If so, we're done.
+        if(bytes<4096)break;
+      
+        // error case.  Must not be Vorbis data
+        System.err.println("Input does not appear to be an Ogg bitstream.");
+        System.exit(1);
+      }
+  
+      // Get the serial number and set up the rest of decode.
+      // serialno first; use it to set up a logical stream
+      os.init(og.serialno());
+    
+      // extract the initial header from the first page and verify that the
+      // Ogg bitstream is in fact Vorbis data
+    
+      // I handle the initial header first instead of just having the code
+      // read all three Vorbis headers at once because reading the initial
+      // header is an easy way to identify a Vorbis bitstream and it's
+      // useful to see that functionality seperated out.
+    
+      vi.init();
+      vc.init();
+      if(os.pagein(og)<0){ 
+        // error; stream version mismatch perhaps
+        System.err.println("Error reading first page of Ogg bitstream data.");
+        System.exit(1);
+      }
+    
+      if(os.packetout(op)!=1){ 
+        // no page? must not be vorbis
+        System.err.println("Error reading initial header packet.");
+        System.exit(1);
+      }
+
+      if(vi.synthesis_headerin(vc,op)<0){ 
+        // error case; not a vorbis header
+        System.err.println("This Ogg bitstream does not contain Vorbis audio data.");
+        System.exit(1);
+      }
+
+      // At this point, we're sure we're Vorbis.  We've set up the logical
+      // (Ogg) bitstream decoder.  Get the comment and codebook headers and
+      // set up the Vorbis decoder
+    
+      // The next two packets in order are the comment and codebook headers.
+      // They're likely large and may span multiple pages.  Thus we reead
+      // and submit data until we get our two pacakets, watching that no
+      // pages are missing.  If a page is missing, error out; losing a
+      // header page is the only place where missing data is fatal. */
+    
+      int i=0;
+      while(i<2){
+        while(i<2){
+
+         int result=oy.pageout(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){
+            os.pagein(og); // we can ignore any errors here
+                          // as they'll also become apparent
+                          // at packetout
+           while(i<2){
+             result=os.packetout(op);
+             if(result==0)break;
+             if(result==-1){
+               // Uh oh; data at some point was corrupted or missing!
+               // We can't tolerate that in a header.  Die.
+               System.err.println("Corrupt secondary header.  Exiting.");
+               System.exit(1);
+             }
+             vi.synthesis_headerin(vc,op);
+             i++;
+           }
+         }
+        }
+        // no harm in not checking before adding more
+        index=oy.buffer(4096);
+        buffer=oy.data; 
+        try{
+          bytes=input.read(buffer, index, 4096);
+       }
+        catch(Exception e){
+          System.err.println(e);
+          System.exit(1);
+       }
+        if(bytes==0 && i<2){
+         System.err.println("End of file before finding all Vorbis headers!");
+          System.exit(1);
+       }
+       oy.wrote(bytes);
+      }
+    
+      // Throw the comments plus a few lines about the bitstream we're
+      // decoding
+      {
+         byte[][] ptr=vc.user_comments;
+         for(int j=0; j<ptr.length;j++){
+           if(ptr[j]==null) break;
+           System.err.println(new String(ptr[j], 0, ptr[j].length-1));
+        } 
+        System.err.println("\nBitstream is "+vi.channels+" channel, "+vi.rate+"Hz");
+        System.err.println("Encoded by: "+new String(vc.vendor, 0, vc.vendor.length-1)+"\n");
+      }
+    
+      convsize=4096/vi.channels;
+
+      // OK, got and parsed all three headers. Initialize the Vorbis
+      //  packet->PCM decoder.
+      vd.synthesis_init(vi); // central decode state
+      vb.init(vd);           // local state for most of the decode
+                             // so multiple block decodes can
+                             // proceed in parallel.  We could init
+                             // multiple vorbis_block structures
+                             // for vd here
+
+      float[][][] _pcm=new float[1][][];
+      int[] _index=new int[vi.channels];
+      // The rest is just a straight decode loop until end of stream
+      while(eos==0){
+        while(eos==0){
+
+         int result=oy.pageout(og);
+         if(result==0)break; // need more data
+         if(result==-1){ // missing or corrupt data at this page position
+           System.err.println("Corrupt&nbs