]> Joshua Wise's Git repositories - patchfork.git/commitdiff
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 or missing data in bitstream; continuing...");
+         }
+         else{
+            os.pagein(og); // can safely ignore errors at
+                          // this point
+           while(true){
+             result=os.packetout(op);
+
+             if(result==0)break; // need more data
+             if(result==-1){ // missing or corrupt data at this page position
+                            // no reason to complain; already complained above
+             }
+              else{
+                // we have a packet.  Decode it
+               int samples;
+               if(vb.synthesis(op)==0){ // test for success!
+                 vd.synthesis_blockin(vb);
+               }
+
+               // **pcm is a multichannel float vector.  In stereo, for
+               // example, pcm[0] is left, and pcm[1] is right.  samples is
+               // the size of each channel.  Convert the float values
+               // (-1.<=range<=1.) to whatever PCM format and write it out
+
+               while((samples=vd.synthesis_pcmout(_pcm, _index))>0){
+                 float[][] pcm=_pcm[0];
+                  boolean clipflag=false;
+                 int bout=(samples<convsize?samples:convsize);
+               
+                 // convert floats 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)(pcm[i][mono+j]*32767.);
+//                   short val=(short)(pcm[i][mono+j]*32767.);
+//                   int val=(int)Math.round(pcm[i][mono+j]*32767.);
+                     // might as well guard against clipping
+                     if(val>32767){
+                       val=32767;
+                       clipflag=true;
+                     }
+                     if(val<-32768){
+                       val=-32768;
+                       clipflag=true;
+                     }
+                      if(val<0) val=val|0x8000;
+                     convbuffer[ptr]=(byte)(val);
+                     convbuffer[ptr+1]=(byte)(val>>>8);
+                     ptr+=2*(vi.channels);
+                   }
+                 }
+               
+                 //if(clipflag)
+                  //  System.err.println("Clipping in frame "+vd.sequence);
+               
+                 System.out.write(convbuffer, 0, 2*vi.channels*bout);
+               
+                 vd.synthesis_read(bout); // tell libvorbis how
+                                          // many samples we
+                                          // actually consumed
+               }           
+             }
+           }
+           if(og.eos()!=0)eos=1;
+         }
+        }
+        if(eos==0){
+         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);
+         if(bytes==0)eos=1;
+        }
+      }
+
+      // clean up this logical bitstream; before exit we see if we're
+      // followed by another [chained]
+
+      os.clear();
+  
+      // ogg_page and ogg_packet structs always point to storage in
+      // libvorbis.  They're never freed or manipulated directly
+    
+      vb.clear();
+      vd.clear();
+      vi.clear();  // must be called last
+    }
+
+    // OK, clean up the framer
+    oy.clear();
+    System.err.println("Done.");
+  }
+}
+
diff --git a/jorbis/src/com/jcraft/jorbis/Drft.java b/jorbis/src/com/jcraft/jorbis/Drft.java
new file mode 100644 (file)
index 0000000..c7ff203
--- /dev/null
@@ -0,0 +1,1317 @@
+/* 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 Drft{
+  int n;
+  float[] trigcache;    
+  int[] splitcache;
+
+  void backward(float[] data){
+    //System.err.println("Drft.backward");
+    if(n==1)return;
+    drftb1(n,data,trigcache,trigcache,n,splitcache);
+  }
+
+  void init(int n){
+    //System.err.println("Drft.init");
+    this.n=n;
+    trigcache=new float[3*n];
+    splitcache=new int[32];
+    fdrffti(n, trigcache, splitcache);
+  }
+
+  void clear(){
+    //System.err.println("Drft.clear");
+    if(trigcache!=null)trigcache=null;
+    if(splitcache!=null)splitcache=null;
+//    memset(l,0,sizeof(drft_lookup));
+  }
+
+  static int[] ntryh = { 4,2,3,5 };
+  static float tpi = 6.28318530717958647692528676655900577f;
+  static float hsqt2 = .70710678118654752440084436210485f;
+  static float taui = .86602540378443864676372317075293618f;
+  static float taur = -.5f;
+  static float sqrt2 = 1.4142135623730950488016887242097f;
+
+  static void drfti1(int n, float[] wa, int index, int[] ifac){
+    float arg,argh,argld,fi;
+    int ntry=0,i,j=-1;
+    int k1, l1, l2, ib;
+    int ld, ii, ip, is, nq, nr;
+    int ido, ipm, nfm1;
+    int nl=n;
+    int nf=0;
+
+    int state=101;
+
+    loop: while(true){
+      switch(state){
+      case 101:
+       j++;
+       if (j < 4)
+         ntry=ntryh[j];
+       else
+         ntry+=2;
+      case 104:
+       nq=nl/ntry;
+       nr=nl-ntry*nq;
+       if(nr!=0){
+         state=101;
+         break;
+       }
+       nf++;
+       ifac[nf+1]=ntry;
+       nl=nq;
+       if(ntry!=2){
+         state=107;
+         break;
+       }
+       if(nf==1){
+         state=107;
+         break;
+       }
+
+       for(i=1;i<nf;i++){
+         ib=nf-i+1;
+         ifac[ib+1]=ifac[ib];
+       }
+       ifac[2] = 2;
+      case 107:
+       if(nl!=1){
+         state=104;
+         break;
+       }
+       ifac[0]=n;
+       ifac[1]=nf;
+       argh=tpi/n;
+       is=0;
+       nfm1=nf-1;
+       l1=1;
+
+       if(nfm1==0)return;
+
+       for (k1=0;k1<nfm1;k1++){
+         ip=ifac[k1+2];
+         ld=0;
+         l2=l1*ip;
+         ido=n/l2;
+         ipm=ip-1;
+
+         for (j=0;j<ipm;j++){
+           ld+=l1;
+           i=is;
+           argld=(float)ld*argh;
+           fi=0.f;
+           for (ii=2;ii<ido;ii+=2){
+             fi+=1.f;
+             arg=fi*argld;
+             wa[index+i++]=(float)Math.cos(arg);
+             wa[index+i++]=(float)Math.sin(arg);
+           }
+           is+=ido;
+         }
+         l1=l2;
+       }
+       break loop;
+      }
+    }
+  }
+
+  static void fdrffti(int n, float[] wsave, int[] ifac){
+//System.err.println("fdrffti: n="+n);
+    if(n == 1) return;
+    drfti1(n, wsave, n, ifac);
+  }
+
+  static void dradf2(int ido,int l1,float[] cc, float[] ch, float[] wa1, int index){
+    int i,k;
+    float ti2,tr2;
+    int t0,t1,t2,t3,t4,t5,t6;
+
+    t1=0;
+    t0=(t2=l1*ido);
+    t3=ido<<1;
+    for(k=0;k<l1;k++){
+      ch[t1<<1]=cc[t1]+cc[t2];
+      ch[(t1<<1)+t3-1]=cc[t1]-cc[t2];
+      t1+=ido;
+      t2+=ido;
+    }
+    
+    if(ido<2)return;
+
+    if(ido!=2){
+      t1=0;
+      t2=t0;
+      for(k=0;k<l1;k++){
+       t3=t2;
+       t4=(t1<<1)+(ido<<1);
+       t5=t1;
+       t6=t1+t1;
+       for(i=2;i<ido;i+=2){
+         t3+=2;
+         t4-=2;
+         t5+=2;
+         t6+=2;
+         tr2=wa1[index+i-2]*cc[t3-1]+wa1[index+i-1]*cc[t3];
+         ti2=wa1[index+i-2]*cc[t3]-wa1[index+i-1]*cc[t3-1];
+         ch[t6]=cc[t5]+ti2;
+         ch[t4]=ti2-cc[t5];
+         ch[t6-1]=cc[t5-1]+tr2;
+         ch[t4-1]=cc[t5-1]-tr2;
+       }
+       t1+=ido;
+       t2+=ido;
+      }
+      if(ido%2==1)return;
+    }
+    
+    t3=(t2=(t1=ido)-1);
+    t2+=t0;
+    for(k=0;k<l1;k++){
+      ch[t1]=-cc[t2];
+      ch[t1-1]=cc[t3];
+      t1+=ido<<1;
+      t2+=ido;
+      t3+=ido;
+    }
+  }
+
+  static void dradf4(int ido,int l1,float[] cc, float[] ch, 
+                    float[] wa1, int index1,
+                    float[] wa2, int index2,
+                    float[] wa3, int index3){
+    int i,k,t0,t1,t2,t3,t4,t5,t6;
+    float ci2,ci3,ci4,cr2,cr3,cr4,ti1,ti2,ti3,ti4,tr1,tr2,tr3,tr4;
+    t0=l1*ido;
+  
+    t1=t0;
+    t4=t1<<1;
+    t2=t1+(t1<<1);
+    t3=0;
+
+    for(k=0;k<l1;k++){
+      tr1=cc[t1]+cc[t2];
+      tr2=cc[t3]+cc[t4];
+
+      ch[t5=t3<<2]=tr1+tr2;
+      ch[(ido<<2)+t5-1]=tr2-tr1;
+      ch[(t5+=(ido<<1))-1]=cc[t3]-cc[t4];
+      ch[t5]=cc[t2]-cc[t1];
+      
+      t1+=ido;
+      t2+=ido;
+      t3+=ido;
+      t4+=ido;
+    }
+    if(ido<2)return;
+
+    if(ido!=2){
+      t1=0;
+      for(k=0;k<l1;k++){
+       t2=t1;
+       t4=t1<<2;
+       t5=(t6=ido<<1)+t4;
+       for(i=2;i<ido;i+=2){
+         t3=(t2+=2);
+         t4+=2;
+         t5-=2;
+
+         t3+=t0;
+         cr2=wa1[index1+i-2]*cc[t3-1]+wa1[index1+i-1]*cc[t3];
+         ci2=wa1[index1+i-2]*cc[t3]-wa1[index1+i-1]*cc[t3-1];
+         t3+=t0;
+         cr3=wa2[index2+i-2]*cc[t3-1]+wa2[index2+i-1]*cc[t3];
+         ci3=wa2[index2+i-2]*cc[t3]-wa2[index2+i-1]*cc[t3-1];
+         t3+=t0;
+         cr4=wa3[index3+i-2]*cc[t3-1]+wa3[index3+i-1]*cc[t3];
+         ci4=wa3[index3+i-2]*cc[t3]-wa3[index3+i-1]*cc[t3-1];
+
+         tr1=cr2+cr4;
+         tr4=cr4-cr2;
+         ti1=ci2+ci4;
+         ti4=ci2-ci4;
+
+         ti2=cc[t2]+ci3;
+         ti3=cc[t2]-ci3;
+         tr2=cc[t2-1]+cr3;
+         tr3=cc[t2-1]-cr3;
+         
+         ch[t4-1]=tr1+tr2;
+         ch[t4]=ti1+ti2;
+
+         ch[t5-1]=tr3-ti4;
+         ch[t5]=tr4-ti3;
+
+         ch[t4+t6-1]=ti4+tr3;
+         ch[t4+t6]=tr4+ti3;
+
+         ch[t5+t6-1]=tr2-tr1;
+         ch[t5+t6]=ti1-ti2;
+       }
+       t1+=ido;
+      }
+      if((ido&1)!=0)return;
+    }
+
+    t2=(t1=t0+ido-1)+(t0<<1);
+    t3=ido<<2;
+    t4=ido;
+    t5=ido<<1;
+    t6=ido;
+
+    for(k=0;k<l1;k++){
+      ti1=-hsqt2*(cc[t1]+cc[t2]);
+      tr1=hsqt2*(cc[t1]-cc[t2]);
+
+      ch[t4-1]=tr1+cc[t6-1];
+      ch[t4+t5-1]=cc[t6-1]-tr1;
+
+      ch[t4]=ti1-cc[t1+t0];
+      ch[t4+t5]=ti1+cc[t1+t0];
+      
+      t1+=ido;
+      t2+=ido;
+      t4+=t3;
+      t6+=ido;
+    }
+  }
+
+  static void dradfg(int ido,int ip,int l1,int idl1,float[] cc,float[] c1,
+                    float[] c2, float[] ch, float[] ch2, float[] wa, int index){
+    int idij,ipph,i,j,k,l,ic,ik,is;
+    int t0,t1,t2=0,t3,t4,t5,t6,t7,t8,t9,t10;
+    float dc2,ai1,ai2,ar1,ar2,ds2;
+    int nbd;
+    float dcp=0,arg,dsp=0,ar1h,ar2h;
+    int idp2,ipp2;
+  
+    arg=tpi/(float)ip;
+    dcp=(float)Math.cos(arg);
+    dsp=(float)Math.sin(arg);
+    ipph=(ip+1)>>1;
+    ipp2=ip;
+    idp2=ido;
+    nbd=(ido-1)>>1;
+    t0=l1*ido;
+    t10=ip*ido;
+
+    int state=100;
+    loop: while(true){
+      switch(state){
+      case 101:
+       if(ido==1){
+         state=119;
+         break;
+       }
+       for(ik=0;ik<idl1;ik++)ch2[ik]=c2[ik];
+
+       t1=0;
+       for(j=1;j<ip;j++){
+         t1+=t0;
+         t2=t1;
+         for(k=0;k<l1;k++){
+           ch[t2]=c1[t2];
+           t2+=ido;
+         }
+       }
+
+       is=-ido;
+       t1=0;
+       if(nbd>l1){
+         for(j=1;j<ip;j++){
+           t1+=t0;
+           is+=ido;
+           t2= -ido+t1;
+           for(k=0;k<l1;k++){
+             idij=is-1;
+             t2+=ido;
+             t3=t2;
+             for(i=2;i<ido;i+=2){
+               idij+=2;
+               t3+=2;
+               ch[t3-1]=wa[index+idij-1]*c1[t3-1]+wa[index+idij]*c1[t3];
+               ch[t3]=wa[index+idij-1]*c1[t3]-wa[index+idij]*c1[t3-1];
+             }
+           }
+         }
+       }
+       else{
+
+         for(j=1;j<ip;j++){
+           is+=ido;
+           idij=is-1;
+           t1+=t0;
+           t2=t1;
+           for(i=2;i<ido;i+=2){
+             idij+=2;
+             t2+=2;
+             t3=t2;
+             for(k=0;k<l1;k++){
+               ch[t3-1]=wa[index+idij-1]*c1[t3-1]+wa[index+idij]*c1[t3];
+               ch[t3]=wa[index+idij-1]*c1[t3]-wa[index+idij]*c1[t3-1];
+               t3+=ido;
+             }
+           }
+         }
+       }
+
+       t1=0;
+       t2=ipp2*t0;
+       if(nbd<l1){
+         for(j=1;j<ipph;j++){
+           t1+=t0;
+           t2-=t0;
+           t3=t1;
+           t4=t2;
+           for(i=2;i<ido;i+=2){
+             t3+=2;
+             t4+=2;
+             t5=t3-ido;
+             t6=t4-ido;
+             for(k=0;k<l1;k++){
+               t5+=ido;
+               t6+=ido;
+               c1[t5-1]=ch[t5-1]+ch[t6-1];
+               c1[t6-1]=ch[t5]-ch[t6];
+               c1[t5]=ch[t5]+ch[t6];
+               c1[t6]=ch[t6-1]-ch[t5-1];
+             }
+           }
+         }
+       }
+       else{
+         for(j=1;j<ipph;j++){
+           t1+=t0;
+           t2-=t0;
+           t3=t1;
+           t4=t2;
+           for(k=0;k<l1;k++){
+             t5=t3;
+             t6=t4;
+             for(i=2;i<ido;i+=2){
+               t5+=2;
+               t6+=2;
+               c1[t5-1]=ch[t5-1]+ch[t6-1];
+               c1[t6-1]=ch[t5]-ch[t6];
+               c1[t5]=ch[t5]+ch[t6];
+               c1[t6]=ch[t6-1]-ch[t5-1];
+             }
+             t3+=ido;
+             t4+=ido;
+           }
+         }
+       }
+      case 119:
+       for(ik=0;ik<idl1;ik++)c2[ik]=ch2[ik];
+
+       t1=0;
+       t2=ipp2*idl1;
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1-ido;
+         t4=t2-ido;
+         for(k=0;k<l1;k++){
+           t3+=ido;
+           t4+=ido;
+           c1[t3]=ch[t3]+ch[t4];
+           c1[t4]=ch[t4]-ch[t3];
+         }
+       }
+
+       ar1=1.f;
+       ai1=0.f;
+       t1=0;
+       t2=ipp2*idl1;
+       t3=(ip-1)*idl1;
+       for(l=1;l<ipph;l++){
+         t1+=idl1;
+         t2-=idl1;
+         ar1h=dcp*ar1-dsp*ai1;
+         ai1=dcp*ai1+dsp*ar1;
+         ar1=ar1h;
+         t4=t1;
+         t5=t2;
+         t6=t3;
+         t7=idl1;
+
+         for(ik=0;ik<idl1;ik++){
+           ch2[t4++]=c2[ik]+ar1*c2[t7++];
+           ch2[t5++]=ai1*c2[t6++];
+         }
+
+         dc2=ar1;
+         ds2=ai1;
+         ar2=ar1;
+         ai2=ai1;
+
+         t4=idl1;
+         t5=(ipp2-1)*idl1;
+         for(j=2;j<ipph;j++){
+           t4+=idl1;
+           t5-=idl1;
+
+           ar2h=dc2*ar2-ds2*ai2;
+           ai2=dc2*ai2+ds2*ar2;
+           ar2=ar2h;
+
+           t6=t1;
+           t7=t2;
+           t8=t4;
+           t9=t5;
+           for(ik=0;ik<idl1;ik++){
+             ch2[t6++]+=ar2*c2[t8++];
+             ch2[t7++]+=ai2*c2[t9++];
+           }
+         }
+       }
+       t1=0;
+       for(j=1;j<ipph;j++){
+         t1+=idl1;
+         t2=t1;
+         for(ik=0;ik<idl1;ik++)ch2[ik]+=c2[t2++];
+       }
+
+       if(ido<l1){
+         state=132;
+         break;
+       }
+
+       t1=0;
+       t2=0;
+       for(k=0;k<l1;k++){
+         t3=t1;
+         t4=t2;
+         for(i=0;i<ido;i++)cc[t4++]=ch[t3++];
+         t1+=ido;
+         t2+=t10;
+       }
+       state=135;
+       break;
+
+      case 132:
+       for(i=0;i<ido;i++){
+         t1=i;
+         t2=i;
+         for(k=0;k<l1;k++){
+           cc[t2]=ch[t1];
+           t1+=ido;
+           t2+=t10;
+         }
+       }
+      case 135:
+       t1=0;
+       t2=ido<<1;
+       t3=0;
+       t4=ipp2*t0;
+       for(j=1;j<ipph;j++){
+         t1+=t2;
+         t3+=t0;
+         t4-=t0;
+
+         t5=t1;
+         t6=t3;
+         t7=t4;
+
+         for(k=0;k<l1;k++){
+           cc[t5-1]=ch[t6];
+           cc[t5]=ch[t7];
+           t5+=t10;
+           t6+=ido;
+           t7+=ido;
+         }
+       }
+
+       if(ido==1)return;
+       if(nbd<l1){
+         state=141;
+         break;
+       }
+
+       t1=-ido;
+       t3=0;
+       t4=0;
+       t5=ipp2*t0;
+       for(j=1;j<ipph;j++){
+         t1+=t2;
+         t3+=t2;
+         t4+=t0;
+         t5-=t0;
+         t6=t1;
+         t7=t3;
+         t8=t4;
+         t9=t5;
+         for(k=0;k<l1;k++){
+           for(i=2;i<ido;i+=2){
+             ic=idp2-i;
+             cc[i+t7-1]=ch[i+t8-1]+ch[i+t9-1];
+             cc[ic+t6-1]=ch[i+t8-1]-ch[i+t9-1];
+             cc[i+t7]=ch[i+t8]+ch[i+t9];
+             cc[ic+t6]=ch[i+t9]-ch[i+t8];
+           }
+           t6+=t10;
+           t7+=t10;
+           t8+=ido;
+           t9+=ido;
+         }
+       }
+       return;
+      case 141:
+       t1=-ido;
+       t3=0;
+       t4=0;
+       t5=ipp2*t0;
+       for(j=1;j<ipph;j++){
+         t1+=t2;
+         t3+=t2;
+         t4+=t0;
+         t5-=t0;
+         for(i=2;i<ido;i+=2){
+           t6=idp2+t1-i;
+           t7=i+t3;
+           t8=i+t4;
+           t9=i+t5;
+           for(k=0;k<l1;k++){
+             cc[t7-1]=ch[t8-1]+ch[t9-1];
+             cc[t6-1]=ch[t8-1]-ch[t9-1];
+             cc[t7]=ch[t8]+ch[t9];
+             cc[t6]=ch[t9]-ch[t8];
+             t6+=t10;
+             t7+=t10;
+             t8+=ido;
+             t9+=ido;
+           }
+         }
+       }
+       break loop;
+      }
+    }
+  }
+
+  static void drftf1(int n,float[] c, float[] ch, float[] wa, int[] ifac){
+    int i,k1,l1,l2;
+    int na,kh,nf;
+    int ip,iw,ido,idl1,ix2,ix3;
+
+    nf=ifac[1];
+    na=1;
+    l2=n;
+    iw=n;
+
+    for(k1=0;k1<nf;k1++){
+      kh=nf-k1;
+      ip=ifac[kh+1];
+      l1=l2/ip;
+      ido=n/l2;
+      idl1=ido*l1;
+      iw-=(ip-1)*ido;
+      na=1-na;
+
+      int state=100;
+      loop: while(true){
+       switch(state){
+       case 100:
+         if(ip!=4){
+           state=102;
+           break;
+         }
+
+         ix2=iw+ido;
+         ix3=ix2+ido;
+         if(na!=0)
+           dradf4(ido,l1,ch,c,wa,iw-1,wa,ix2-1,wa,ix3-1);
+         else
+           dradf4(ido,l1,c,ch,wa,iw-1,wa,ix2-1,wa,ix3-1);
+         state=110;
+         break;
+       case 102:
+         if(ip!=2){
+           state=104;
+           break;
+         }
+         if(na!=0){
+           state=103;
+           break;
+         }
+         dradf2(ido,l1,c,ch,wa, iw-1);
+         state=110;
+         break;
+       case 103:
+         dradf2(ido,l1,ch,c,wa, iw-1);
+       case 104:
+         if(ido==1)na=1-na;
+         if(na!=0){
+           state=109;
+           break;
+         }
+         dradfg(ido,ip,l1,idl1,c,c,c,ch,ch,wa,iw-1);
+         na=1;
+         state=110;
+         break;
+       case 109:
+         dradfg(ido,ip,l1,idl1,ch,ch,ch,c,c,wa,iw-1);
+         na=0;
+       case 110:
+         l2=l1;
+         break loop;
+       }
+      }
+    }
+    if(na==1)return;
+    for(i=0;i<n;i++)c[i]=ch[i];
+  }
+
+  static void dradb2(int ido,int l1,float[] cc,float[] ch,float[] wa1, int index){
+    int i,k,t0,t1,t2,t3,t4,t5,t6;
+    float ti2,tr2;
+
+    t0=l1*ido;
+    
+    t1=0;
+    t2=0;
+    t3=(ido<<1)-1;
+    for(k=0;k<l1;k++){
+      ch[t1]=cc[t2]+cc[t3+t2];
+      ch[t1+t0]=cc[t2]-cc[t3+t2];
+      t2=(t1+=ido)<<1;
+    }
+
+    if(ido<2)return;
+    if(ido!=2){
+      t1=0;
+      t2=0;
+      for(k=0;k<l1;k++){
+       t3=t1;
+       t5=(t4=t2)+(ido<<1);
+       t6=t0+t1;
+       for(i=2;i<ido;i+=2){
+         t3+=2;
+         t4+=2;
+         t5-=2;
+         t6+=2;
+         ch[t3-1]=cc[t4-1]+cc[t5-1];
+         tr2=cc[t4-1]-cc[t5-1];
+         ch[t3]=cc[t4]-cc[t5];
+         ti2=cc[t4]+cc[t5];
+         ch[t6-1]=wa1[index+i-2]*tr2-wa1[index+i-1]*ti2;
+         ch[t6]=wa1[index+i-2]*ti2+wa1[index+i-1]*tr2;
+       }
+       t2=(t1+=ido)<<1;
+      }
+      if((ido%2)==1)return;
+    }
+
+    t1=ido-1;
+    t2=ido-1;
+    for(k=0;k<l1;k++){
+      ch[t1]=cc[t2]+cc[t2];
+      ch[t1+t0]=-(cc[t2+1]+cc[t2+1]);
+      t1+=ido;
+      t2+=ido<<1;
+    }
+  }
+
+  static void dradb3(int ido,int l1,float[] cc,float[] ch,
+                    float[] wa1, int index1,
+                    float[] wa2, int index2){
+    int i,k,t0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10;
+    float ci2,ci3,di2,di3,cr2,cr3,dr2,dr3,ti2,tr2;
+    t0=l1*ido;
+
+    t1=0;
+    t2=t0<<1;
+    t3=ido<<1;
+    t4=ido+(ido<<1);
+    t5=0;
+    for(k=0;k<l1;k++){
+      tr2=cc[t3-1]+cc[t3-1];
+      cr2=cc[t5]+(taur*tr2);
+      ch[t1]=cc[t5]+tr2;
+      ci3=taui*(cc[t3]+cc[t3]);
+      ch[t1+t0]=cr2-ci3;
+      ch[t1+t2]=cr2+ci3;
+      t1+=ido;
+      t3+=t4;
+      t5+=t4;
+    }
+
+    if(ido==1)return;
+
+    t1=0;
+    t3=ido<<1;
+    for(k=0;k<l1;k++){
+      t7=t1+(t1<<1);
+      t6=(t5=t7+t3);
+      t8=t1;
+      t10=(t9=t1+t0)+t0;
+
+      for(i=2;i<ido;i+=2){
+       t5+=2;
+       t6-=2;
+       t7+=2;
+       t8+=2;
+       t9+=2;
+       t10+=2;
+       tr2=cc[t5-1]+cc[t6-1];
+       cr2=cc[t7-1]+(taur*tr2);
+       ch[t8-1]=cc[t7-1]+tr2;
+       ti2=cc[t5]-cc[t6];
+       ci2=cc[t7]+(taur*ti2);
+       ch[t8]=cc[t7]+ti2;
+       cr3=taui*(cc[t5-1]-cc[t6-1]);
+       ci3=taui*(cc[t5]+cc[t6]);
+       dr2=cr2-ci3;
+       dr3=cr2+ci3;
+       di2=ci2+cr3;
+       di3=ci2-cr3;
+       ch[t9-1]=wa1[index1+i-2]*dr2-wa1[index1+i-1]*di2;
+       ch[t9]=wa1[index1+i-2]*di2+wa1[index1+i-1]*dr2;
+       ch[t10-1]=wa2[index2+i-2]*dr3-wa2[index2+i-1]*di3;
+       ch[t10]=wa2[index2+i-2]*di3+wa2[index2+i-1]*dr3;
+      }
+      t1+=ido;
+    }
+  }
+
+  static void dradb4(int ido,int l1,float[] cc,float[] ch,
+                    float[] wa1, int index1,
+                    float[] wa2, int index2,
+                    float[] wa3, int index3){
+    int i,k,t0,t1,t2,t3,t4,t5,t6,t7,t8;
+    float ci2,ci3,ci4,cr2,cr3,cr4,ti1,ti2,ti3,ti4,tr1,tr2,tr3,tr4;
+    t0=l1*ido;
+  
+    t1=0;
+    t2=ido<<2;
+    t3=0;
+    t6=ido<<1;
+    for(k=0;k<l1;k++){
+      t4=t3+t6;
+      t5=t1;
+      tr3=cc[t4-1]+cc[t4-1];
+      tr4=cc[t4]+cc[t4]; 
+      tr1=cc[t3]-cc[(t4+=t6)-1];
+      tr2=cc[t3]+cc[t4-1];
+      ch[t5]=tr2+tr3;
+      ch[t5+=t0]=tr1-tr4;
+      ch[t5+=t0]=tr2-tr3;
+      ch[t5+=t0]=tr1+tr4;
+      t1+=ido;
+      t3+=t2;
+    }
+
+    if(ido<2)return;
+    if(ido!=2){
+      t1=0;
+      for(k=0;k<l1;k++){
+       t5=(t4=(t3=(t2=t1<<2)+t6))+t6;
+       t7=t1;
+       for(i=2;i<ido;i+=2){
+         t2+=2;
+         t3+=2;
+         t4-=2;
+         t5-=2;
+         t7+=2;
+         ti1=cc[t2]+cc[t5];
+         ti2=cc[t2]-cc[t5];
+         ti3=cc[t3]-cc[t4];
+         tr4=cc[t3]+cc[t4];
+         tr1=cc[t2-1]-cc[t5-1];
+         tr2=cc[t2-1]+cc[t5-1];
+         ti4=cc[t3-1]-cc[t4-1];
+         tr3=cc[t3-1]+cc[t4-1];
+         ch[t7-1]=tr2+tr3;
+         cr3=tr2-tr3;
+         ch[t7]=ti2+ti3;
+         ci3=ti2-ti3;
+         cr2=tr1-tr4;
+         cr4=tr1+tr4;
+         ci2=ti1+ti4;
+         ci4=ti1-ti4;
+
+         ch[(t8=t7+t0)-1]=wa1[index1+i-2]*cr2-wa1[index1+i-1]*ci2;
+         ch[t8]=wa1[index1+i-2]*ci2+wa1[index1+i-1]*cr2;
+         ch[(t8+=t0)-1]=wa2[index2+i-2]*cr3-wa2[index2+i-1]*ci3;
+         ch[t8]=wa2[index2+i-2]*ci3+wa2[index2+i-1]*cr3;
+         ch[(t8+=t0)-1]=wa3[index3+i-2]*cr4-wa3[index3+i-1]*ci4;
+         ch[t8]=wa3[index3+i-2]*ci4+wa3[index3+i-1]*cr4;
+       }
+       t1+=ido;
+      }
+      if(ido%2 == 1)return;
+    }
+
+    t1=ido;
+    t2=ido<<2;
+    t3=ido-1;
+    t4=ido+(ido<<1);
+    for(k=0;k<l1;k++){
+      t5=t3;
+      ti1=cc[t1]+cc[t4];
+      ti2=cc[t4]-cc[t1];
+      tr1=cc[t1-1]-cc[t4-1];
+      tr2=cc[t1-1]+cc[t4-1];
+      ch[t5]=tr2+tr2;
+      ch[t5+=t0]=sqrt2*(tr1-ti1);
+      ch[t5+=t0]=ti2+ti2;
+      ch[t5+=t0]=-sqrt2*(tr1+ti1);
+      
+      t3+=ido;
+      t1+=t2;
+      t4+=t2;
+    }
+  }
+
+  static void dradbg(int ido,int ip,int l1,int idl1,float[] cc,float[] c1,
+                    float[] c2,float[] ch,float[] ch2,float[] wa, int index ){
+
+    int idij,ipph=0,i,j,k,l,ik,is,t0=0,t1,t2,t3,t4,t5,t6,t7,t8,t9,t10=0,
+      t11,t12;
+    float dc2,ai1,ai2,ar1,ar2,ds2;
+    int nbd=0;
+    float dcp=0,arg,dsp=0,ar1h,ar2h;
+    int ipp2=0;
+
+    int state=100;
+
+    loop: while(true){
+      switch(state){
+      case 100:
+       t10=ip*ido;
+       t0=l1*ido;
+       arg=tpi/(float)ip;
+       dcp=(float)Math.cos(arg);
+       dsp=(float)Math.sin(arg);
+       nbd=(ido-1)>>>1;
+       ipp2=ip;
+       ipph=(ip+1)>>>1;
+       if(ido<l1){
+         state=103;
+         break;
+       }
+       t1=0;
+       t2=0;
+       for(k=0;k<l1;k++){
+         t3=t1;
+         t4=t2;
+         for(i=0;i<ido;i++){
+           ch[t3]=cc[t4];
+           t3++;
+           t4++;
+         }
+         t1+=ido;
+         t2+=t10;
+       }
+       state=106;
+       break;
+      case 103:
+       t1=0;
+       for(i=0;i<ido;i++){
+         t2=t1;
+         t3=t1;
+         for(k=0;k<l1;k++){
+           ch[t2]=cc[t3];
+           t2+=ido;
+           t3+=t10;
+         }
+         t1++;
+       }
+      case 106:
+       t1=0;
+       t2=ipp2*t0;
+       t7=(t5=ido<<1);
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1;
+         t4=t2;
+         t6=t5;
+         for(k=0;k<l1;k++){
+           ch[t3]=cc[t6-1]+cc[t6-1];
+           ch[t4]=cc[t6]+cc[t6];
+           t3+=ido;
+           t4+=ido;
+           t6+=t10;
+         }
+         t5+=t7;
+       }
+       if (ido == 1){
+         state=116;
+         break;
+       }
+       if(nbd<l1){
+         state=112;
+         break;
+       }
+
+       t1=0;
+       t2=ipp2*t0;
+       t7=0;
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1;
+         t4=t2;
+
+         t7+=(ido<<1);
+         t8=t7;
+         for(k=0;k<l1;k++){
+           t5=t3;
+           t6=t4;
+           t9=t8;
+           t11=t8;
+           for(i=2;i<ido;i+=2){
+             t5+=2;
+             t6+=2;
+             t9+=2;
+             t11-=2;
+             ch[t5-1]=cc[t9-1]+cc[t11-1];
+             ch[t6-1]=cc[t9-1]-cc[t11-1];
+             ch[t5]=cc[t9]-cc[t11];
+             ch[t6]=cc[t9]+cc[t11];
+           }
+           t3+=ido;
+           t4+=ido;
+           t8+=t10;
+         }
+       }
+       state=116;
+       break;
+      case 112:
+       t1=0;
+       t2=ipp2*t0;
+       t7=0;
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1;
+         t4=t2;
+         t7+=(ido<<1);
+         t8=t7;
+         t9=t7;
+         for(i=2;i<ido;i+=2){
+           t3+=2;
+           t4+=2;
+           t8+=2;
+           t9-=2;
+           t5=t3;
+           t6=t4;
+           t11=t8;
+           t12=t9;
+           for(k=0;k<l1;k++){
+             ch[t5-1]=cc[t11-1]+cc[t12-1];
+             ch[t6-1]=cc[t11-1]-cc[t12-1];
+             ch[t5]=cc[t11]-cc[t12];
+             ch[t6]=cc[t11]+cc[t12];
+             t5+=ido;
+             t6+=ido;
+             t11+=t10;
+             t12+=t10;
+           }
+         }
+       }
+      case 116:
+       ar1=1.f;
+       ai1=0.f;
+       t1=0;
+       t9=(t2=ipp2*idl1);
+       t3=(ip-1)*idl1;
+       for(l=1;l<ipph;l++){
+         t1+=idl1;
+         t2-=idl1;
+         
+         ar1h=dcp*ar1-dsp*ai1;
+         ai1=dcp*ai1+dsp*ar1;
+         ar1=ar1h;
+         t4=t1;
+         t5=t2;
+         t6=0;
+         t7=idl1;
+         t8=t3;
+         for(ik=0;ik<idl1;ik++){
+           c2[t4++]=ch2[t6++]+ar1*ch2[t7++];
+           c2[t5++]=ai1*ch2[t8++];
+         }
+         dc2=ar1;
+         ds2=ai1;
+         ar2=ar1;
+         ai2=ai1;
+
+         t6=idl1;
+         t7=t9-idl1;
+         for(j=2;j<ipph;j++){
+           t6+=idl1;
+           t7-=idl1;
+           ar2h=dc2*ar2-ds2*ai2;
+           ai2=dc2*ai2+ds2*ar2;
+           ar2=ar2h;
+           t4=t1;
+           t5=t2;
+           t11=t6;
+           t12=t7;
+           for(ik=0;ik<idl1;ik++){
+             c2[t4++]+=ar2*ch2[t11++];
+             c2[t5++]+=ai2*ch2[t12++];
+           }
+         }
+       }
+
+       t1=0;
+       for(j=1;j<ipph;j++){
+         t1+=idl1;
+         t2=t1;
+         for(ik=0;ik<idl1;ik++)ch2[ik]+=ch2[t2++];
+       }
+
+       t1=0;
+       t2=ipp2*t0;
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1;
+         t4=t2;
+         for(k=0;k<l1;k++){
+           ch[t3]=c1[t3]-c1[t4];
+           ch[t4]=c1[t3]+c1[t4];
+           t3+=ido;
+           t4+=ido;
+         }
+       }
+
+       if(ido==1){
+         state=132;
+         break;
+       }
+       if(nbd<l1){
+         state=128;
+         break;
+       }
+
+       t1=0;
+       t2=ipp2*t0;
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1;
+         t4=t2;
+         for(k=0;k<l1;k++){
+           t5=t3;
+           t6=t4;
+           for(i=2;i<ido;i+=2){
+             t5+=2;
+             t6+=2;
+             ch[t5-1]=c1[t5-1]-c1[t6];
+             ch[t6-1]=c1[t5-1]+c1[t6];
+             ch[t5]=c1[t5]+c1[t6-1];
+             ch[t6]=c1[t5]-c1[t6-1];
+           }
+           t3+=ido;
+           t4+=ido;
+         }
+       }
+       state=132;
+       break;
+      case 128:
+       t1=0;
+       t2=ipp2*t0;
+       for(j=1;j<ipph;j++){
+         t1+=t0;
+         t2-=t0;
+         t3=t1;
+         t4=t2;
+         for(i=2;i<ido;i+=2){
+           t3+=2;
+           t4+=2;
+           t5=t3;
+           t6=t4;
+           for(k=0;k<l1;k++){
+             ch[t5-1]=c1[t5-1]-c1[t6];
+             ch[t6-1]=c1[t5-1]+c1[t6];
+             ch[t5]=c1[t5]+c1[t6-1];
+             ch[t6]=c1[t5]-c1[t6-1];
+             t5+=ido;
+             t6+=ido;
+           }
+         }
+       }
+      case 132:
+       if(ido==1)return;
+
+       for(ik=0;ik<idl1;ik++)c2[ik]=ch2[ik];
+
+       t1=0;
+       for(j=1;j<ip;j++){
+         t2=(t1+=t0);
+         for(k=0;k<l1;k++){
+           c1[t2]=ch[t2];
+           t2+=ido;
+         }
+       }
+
+       if(nbd>l1){
+         state=139;
+         break;
+       }
+
+       is= -ido-1;
+       t1=0;
+       for(j=1;j<ip;j++){
+         is+=ido;
+         t1+=t0;
+         idij=is;
+         t2=t1;
+         for(i=2;i<ido;i+=2){
+           t2+=2;
+           idij+=2;
+           t3=t2;
+           for(k=0;k<l1;k++){
+             c1[t3-1]=wa[index+idij-1]*ch[t3-1]-wa[index+idij]*ch[t3];
+             c1[t3]=wa[index+idij-1]*ch[t3]+wa[index+idij]*ch[t3-1];
+             t3+=ido;
+           }
+         }
+       }
+       return;
+
+      case 139:
+       is= -ido-1;
+       t1=0;
+       for(j=1;j<ip;j++){
+         is+=ido;
+         t1+=t0;
+         t2=t1;
+         for(k=0;k<l1;k++){
+           idij=is;
+           t3=t2;
+           for(i=2;i<ido;i+=2){
+             idij+=2;
+             t3+=2;
+             c1[t3-1]=wa[index+idij-1]*ch[t3-1]-wa[index+idij]*ch[t3];
+             c1[t3]=wa[index+idij-1]*ch[t3]+wa[index+idij]*ch[t3-1];
+           }
+           t2+=ido;
+         }
+       }
+       break loop; 
+      }
+    }
+  }
+
+  static void drftb1(int n, float[] c, float[] ch, float[] wa, int index, int[] ifac){
+    int i,k1,l1,l2=0;
+    int na;
+    int nf,ip=0,iw,ix2,ix3,ido=0,idl1=0;
+
+    nf=ifac[1];
+    na=0;
+    l1=1;
+    iw=1;
+
+    for(k1=0;k1<nf;k1++){
+      int state=100;
+      loop: while(true){
+       switch(state){
+       case 100:
+         ip=ifac[k1 + 2];
+         l2=ip*l1;
+         ido=n/l2;
+         idl1=ido*l1;
+         if(ip!=4){
+           state=103;
+           break;
+         }
+         ix2=iw+ido;
+         ix3=ix2+ido;
+
+         if(na!=0)
+           dradb4(ido,l1,ch,c,wa,index+iw-1,wa,index+ix2-1,wa,index+ix3-1);
+         else
+           dradb4(ido,l1,c,ch,wa,index+iw-1,wa,index+ix2-1,wa,index+ix3-1);
+         na=1-na;
+         state=115;
+         break;
+       case 103:
+         if(ip!=2){
+           state=106;
+           break;
+         }
+
+         if(na!=0)
+           dradb2(ido,l1,ch,c,wa,index+iw-1);
+         else
+           dradb2(ido,l1,c,ch,wa,index+iw-1);
+         na=1-na;
+         state=115;
+         break;
+
+       case 106:
+         if(ip!=3){
+           state=109;
+           break;
+         }
+
+         ix2=iw+ido;
+         if(na!=0)
+           dradb3(ido,l1,ch,c,wa,index+iw-1,wa,index+ix2-1);
+         else
+           dradb3(ido,l1,c,ch,wa,index+iw-1,wa,index+ix2-1);
+         na=1-na;
+         state=115;
+         break;
+       case 109:
+         //    The radix five case can be translated later.....
+         //    if(ip!=5)goto L112;
+          //
+         //ix2=iw+ido;
+         //ix3=ix2+ido;
+         //ix4=ix3+ido;
+         //if(na!=0)
+         //  dradb5(ido,l1,ch,c,wa+iw-1,wa+ix2-1,wa+ix3-1,wa+ix4-1);
+         //else
+         //  dradb5(ido,l1,c,ch,wa+iw-1,wa+ix2-1,wa+ix3-1,wa+ix4-1);
+         //na=1-na;
+         //state=115; 
+          //break;
+         if(na!=0)
+           dradbg(ido,ip,l1,idl1,ch,ch,ch,c,c,wa,index+iw-1);
+         else
+           dradbg(ido,ip,l1,idl1,c,c,c,ch,ch,wa,index+iw-1);
+         if(ido==1)na=1-na;
+
+       case 115:
+         l1=l2;
+         iw+=(ip-1)*ido;
+         break loop;
+       }
+      }
+    }
+    if(na==0)return;
+    for(i=0;i<n;i++)c[i]=ch[i];
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/DspState.java b/jorbis/src/com/jcraft/jorbis/DspState.java
new file mode 100644 (file)
index 0000000..5676f64
--- /dev/null
@@ -0,0 +1,459 @@
+/* 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;
+
+public class DspState{
+  static final float M_PI=3.1415926539f;
+  static final int VI_TRANSFORMB=1;
+  static final int VI_WINDOWB=1;
+
+  int analysisp;
+  Info vi;
+  int modebits;
+
+  float[][] pcm;
+  //float[][] pcmret;
+  int      pcm_storage;
+  int      pcm_current;
+  int      pcm_returned;
+
+  float[]  multipliers;
+  int      envelope_storage;
+  int      envelope_current;
+
+  int  eofflag;
+
+  int lW;
+  int W;
+  int nW;
+  int centerW;
+
+  long granulepos;
+  long sequence;
+
+  long glue_bits;
+  long time_bits;
+  long floor_bits;
+  long res_bits;
+
+  // local lookup storage
+//!!  Envelope ve=new Envelope(); // envelope
+//float                **window[2][2][2]; // block, leadin, leadout, type
+  float[][][][][] window;                 // block, leadin, leadout, type
+  //vorbis_look_transform **transform[2];    // block, type 
+  Object[][] transform;
+  CodeBook[] fullbooks;
+  // backend lookups are tied to the mode, not the backend or naked mapping
+  Object[] mode;
+
+  // local storage, only used on the encoding side.  This way the
+  // application does not need to worry about freeing some packets'
+  // memory and not others'; packet storage is always tracked.
+  // Cleared next call to a _dsp_ function
+  byte[] header;
+  byte[] header1;
+  byte[] header2;
+
+  public DspState(){
+    transform=new Object[2][];
+    window=new float[2][][][][];
+    window[0]=new float[2][][][];
+    window[0][0]=new float[2][][];
+    window[0][1]=new float[2][][];
+    window[0][0][0]=new float[2][];
+    window[0][0][1]=new float[2][];
+    window[0][1][0]=new float[2][];
+    window[0][1][1]=new float[2][];
+    window[1]=new float[2][][][];
+    window[1][0]=new float[2][][];
+    window[1][1]=new float[2][][];
+    window[1][0][0]=new float[2][];
+    window[1][0][1]=new float[2][];
+    window[1][1][0]=new float[2][];
+    window[1][1][1]=new float[2][];
+  }
+
+  private static int ilog2(int v){
+    int ret=0;
+    while(v>1){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+  static float[] window(int type, int window, int left, int right){
+    float[] ret=new float[window];
+    switch(type){
+    case 0:
+      // The 'vorbis window' (window 0) is sin(sin(x)*sin(x)*2pi)
+      {
+       int leftbegin=window/4-left/2;
+       int rightbegin=window-window/4-right/2;
+    
+       for(int i=0;i<left;i++){
+         float x=(float)((i+.5)/left*M_PI/2.);
+         x=(float)Math.sin(x);
+         x*=x;
+         x*=M_PI/2.;
+         x=(float)Math.sin(x);
+         ret[i+leftbegin]=x;
+       }
+      
+       for(int i=leftbegin+left;i<rightbegin;i++){
+         ret[i]=1.f;
+       }
+      
+       for(int i=0;i<right;i++){
+         float x=(float)((right-i-.5)/right*M_PI/2.);
+         x=(float)Math.sin(x);
+         x*=x;
+         x*=M_PI/2.;
+         x=(float)Math.sin(x);
+         ret[i+rightbegin]=x;
+       }
+      }
+      break;
+    default:
+      //free(ret);
+      return(null);
+    }
+    return(ret);
+  }
+
+  // Analysis side code, but directly related to blocking.  Thus it's
+  // here and not in analysis.c (which is for analysis transforms only).
+  // The init is here because some of it is shared
+
+  int init(Info vi, boolean encp){
+//System.err.println("DspState.init: vi="+vi+", encp="+encp);
+    //memset(v,0,sizeof(vorbis_dsp_state));
+    this.vi=vi;
+    modebits=ilog2(vi.modes);
+
+    transform[0]=new Object[VI_TRANSFORMB];
+    transform[1]=new Object[VI_TRANSFORMB];
+
+    // MDCT is tranform 0
+
+    transform[0][0]=new Mdct();
+    transform[1][0]=new Mdct();
+    ((Mdct)transform[0][0]).init(vi.blocksizes[0]);
+    ((Mdct)transform[1][0]).init(vi.blocksizes[1]);
+
+    window[0][0][0]=new float[VI_WINDOWB][];
+    window[0][0][1]=window[0][0][0];
+    window[0][1][0]=window[0][0][0];
+    window[0][1][1]=window[0][0][0];
+    window[1][0][0]=new float[VI_WINDOWB][];
+    window[1][0][1]=new float[VI_WINDOWB][];
+    window[1][1][0]=new float[VI_WINDOWB][];
+    window[1][1][1]=new float[VI_WINDOWB][];
+
+    for(int i=0;i<VI_WINDOWB;i++){
+      window[0][0][0][i]=
+       window(i,vi.blocksizes[0],vi.blocksizes[0]/2,vi.blocksizes[0]/2);
+      window[1][0][0][i]=
+       window(i,vi.blocksizes[1],vi.blocksizes[0]/2,vi.blocksizes[0]/2);
+      window[1][0][1][i]=
+       window(i,vi.blocksizes[1],vi.blocksizes[0]/2,vi.blocksizes[1]/2);
+      window[1][1][0][i]=
+       window(i,vi.blocksizes[1],vi.blocksizes[1]/2,vi.blocksizes[0]/2);
+      window[1][1][1][i]=
+       window(i,vi.blocksizes[1],vi.blocksizes[1]/2,vi.blocksizes[1]/2);
+    }
+
+//    if(encp){ // encode/decode differ here
+//      // finish the codebooks
+//      fullbooks=new CodeBook[vi.books];
+//      for(int i=0;i<vi.books;i++){
+//     fullbooks[i]=new CodeBook();
+//     fullbooks[i].init_encode(vi.book_param[i]);
+//      }
+//      analysisp=1;
+//    }
+//    else{
+      // finish the codebooks
+      fullbooks=new CodeBook[vi.books];
+      for(int i=0;i<vi.books;i++){
+       fullbooks[i]=new CodeBook();
+       fullbooks[i].init_decode(vi.book_param[i]);
+      }
+//    }
+
+    // initialize the storage vectors to a decent size greater than the
+    // minimum
+  
+    pcm_storage=8192; // we'll assume later that we have
+                        // a minimum of twice the blocksize of
+                       // accumulated samples in analysis
+    pcm=new float[vi.channels][];
+    //pcmret=new float[vi.channels][];
+    {
+      for(int i=0;i<vi.channels;i++){
+       pcm[i]=new float[pcm_storage];
+      }
+    }
+
+    // all 1 (large block) or 0 (small block)
+    // explicitly set for the sake of clarity
+    lW=0; // previous window size
+    W=0;  // current window size
+
+    // all vector indexes; multiples of samples_per_envelope_step
+    centerW=vi.blocksizes[1]/2;
+
+    pcm_current=centerW;
+
+    // initialize all the mapping/backend lookups
+    mode=new Object[vi.modes];
+    for(int i=0;i<vi.modes;i++){
+      int mapnum=vi.mode_param[i].mapping;
+      int maptype=vi.map_type[mapnum];
+      mode[i]=FuncMapping.mapping_P[maptype].look(this,vi.mode_param[i], 
+                                                 vi.map_param[mapnum]);
+    }
+    return(0);
+  }
+
+  public int synthesis_init(Info vi){
+    init(vi, false);
+    // Adjust centerW to allow an easier mechanism for determining output
+    pcm_returned=centerW;
+    centerW-= vi.blocksizes[W]/4+vi.blocksizes[lW]/4;
+    granulepos=-1;
+    sequence=-1;
+    return(0);
+  }
+
+  DspState(Info vi){
+    this();
+    init(vi, false);
+    // Adjust centerW to allow an easier mechanism for determining output
+    pcm_returned=centerW;
+    centerW-= vi.blocksizes[W]/4+vi.blocksizes[lW]/4;
+    granulepos=-1;
+    sequence=-1;
+  }
+
+  // Unike in analysis, the window is only partially applied for each
+  // block.  The time domain envelope is not yet handled at the point of
+  // calling (as it relies on the previous block).
+
+  public int synthesis_blockin(Block vb){
+    // Shift out any PCM/multipliers that we returned previously
+    // centerW is currently the center of the last block added
+    if(centerW>vi.blocksizes[1]/2 && pcm_returned>8192){
+      // don't shift too much; we need to have a minimum PCM buffer of
+      // 1/2 long block
+
+      int shiftPCM=centerW-vi.blocksizes[1]/2;
+      shiftPCM=(pcm_returned<shiftPCM?pcm_returned:shiftPCM);
+
+      pcm_current-=shiftPCM;
+      centerW-=shiftPCM;
+      pcm_returned-=shiftPCM;
+      if(shiftPCM!=0){
+       for(int i=0;i<vi.channels;i++){
+         System.arraycopy(pcm[i], shiftPCM, pcm[i], 0, pcm_current);
+       }
+      }
+    }
+
+    lW=W;
+    W=vb.W;
+    nW=-1;
+
+    glue_bits+=vb.glue_bits;
+    time_bits+=vb.time_bits;
+    floor_bits+=vb.floor_bits;
+    res_bits+=vb.res_bits;
+
+    if(sequence+1 != vb.sequence)granulepos=-1; // out of sequence; lose count
+
+    sequence=vb.sequence;
+
+    {
+      int sizeW=vi.blocksizes[W];
+      int _centerW=centerW+vi.blocksizes[lW]/4+sizeW/4;
+      int beginW=_centerW-sizeW/2;
+      int endW=beginW+sizeW;
+      int beginSl=0;
+      int endSl=0;
+
+      // Do we have enough PCM/mult storage for the block?
+      if(endW>pcm_storage){
+       // expand the storage
+       pcm_storage=endW+vi.blocksizes[1];
+       for(int i=0;i<vi.channels;i++){
+          float[] foo=new float[pcm_storage];
+         System.arraycopy(pcm[i], 0, foo, 0, pcm[i].length);
+         pcm[i]=foo;
+       }
+      }
+
+      // overlap/add PCM
+      switch(W){
+      case 0:
+       beginSl=0;
+       endSl=vi.blocksizes[0]/2;
+       break;
+      case 1:
+       beginSl=vi.blocksizes[1]/4-vi.blocksizes[lW]/4;
+       endSl=beginSl+vi.blocksizes[lW]/2;
+       break;
+      }
+
+      for(int j=0;j<vi.channels;j++){
+       int _pcm=beginW;
+       // the overlap/add section
+       int i=0;
+       for(i=beginSl;i<endSl;i++){
+         pcm[j][_pcm+i]+=vb.pcm[j][i];
+       }
+       // the remaining section
+       for(;i<sizeW;i++){
+         pcm[j][_pcm+i]=vb.pcm[j][i];
+       }
+      }
+
+      // track the frame number... This is for convenience, but also
+      // making sure our last packet doesn't end with added padding.  If
+      // the last packet is partial, the number of samples we'll have to
+      // return will be past the vb->granulepos.
+      //       
+      // This is not foolproof!  It will be confused if we begin
+      // decoding at the last page after a seek or hole.  In that case,
+      // we don't have a starting point to judge where the last frame
+      // is.  For this reason, vorbisfile will always try to make sure
+      // it reads the last two marked pages in proper sequence
+
+      if(granulepos==-1){
+       granulepos=vb.granulepos;
+      }
+      else{
+       granulepos+=(_centerW-centerW);
+       if(vb.granulepos!=-1 && granulepos!=vb.granulepos){
+         if(granulepos>vb.granulepos && vb.eofflag!=0){
+           // partial last frame.  Strip the padding off
+           _centerW-=(granulepos-vb.granulepos);
+         }// else{ Shouldn't happen *unless* the bitstream is out of
+           // spec.  Either way, believe the bitstream }
+         granulepos=vb.granulepos;
+       }
+      }
+
+      // Update, cleanup
+
+      centerW=_centerW;
+      pcm_current=endW;
+      if(vb.eofflag!=0)eofflag=1;
+    }
+    return(0);
+  }
+
+  // pcm==NULL indicates we just want the pending samples, no more
+  public int synthesis_pcmout(float[][][] _pcm, int[] index){
+    if(pcm_returned<centerW){
+      if(_pcm!=null){
+       for(int i=0;i<vi.channels;i++){
+//       pcmret[i]=pcm[i]+v.pcm_returned;
+//!!!!!!!!
+          index[i]=pcm_returned;
+       }
+       _pcm[0]=pcm;
+      }
+      return(centerW-pcm_returned);
+    }
+    return(0);
+  }
+
+  public int synthesis_read(int bytes){
+    if(bytes!=0 && pcm_returned+bytes>centerW)return(-1);
+    pcm_returned+=bytes;
+    return(0);
+  }
+
+  public void clear(){
+/*
+    if(window[0][0][0]!=0){
+      for(i=0;i<VI_WINDOWB;i++)
+       if(v->window[0][0][0][i])free(v->window[0][0][0][i]);
+      free(v->window[0][0][0]);
+
+      for(j=0;j<2;j++)
+       for(k=0;k<2;k++){
+         for(i=0;i<VI_WINDOWB;i++)
+           if(v->window[1][j][k][i])free(v->window[1][j][k][i]);
+         free(v->window[1][j][k]);
+       }
+    }
+    
+    if(v->pcm){
+      for(i=0;i<vi->channels;i++)
+       if(v->pcm[i])free(v->pcm[i]);
+      free(v->pcm);
+      if(v->pcmret)free(v->pcmret);
+    }
+    if(v->multipliers)free(v->multipliers);
+
+    _ve_envelope_clear(&v->ve);
+    if(v->transform[0]){
+      mdct_clear(v->transform[0][0]);
+      free(v->transform[0][0]);
+      free(v->transform[0]);
+    }
+    if(v->transform[1]){
+      mdct_clear(v->transform[1][0]);
+      free(v->transform[1][0]);
+      free(v->transform[1]);
+    }
+
+    // free mode lookups; these are actually vorbis_look_mapping structs
+    if(vi){
+      for(i=0;i<vi->modes;i++){
+       int mapnum=vi->mode_param[i]->mapping;
+       int maptype=vi->map_type[mapnum];
+       _mapping_P[maptype]->free_look(v->mode[i]);
+      }
+      // free codebooks
+      for(i=0;i<vi->books;i++)
+       vorbis_book_clear(v->fullbooks+i);
+    }
+
+    if(v->mode)free(v->mode);    
+    if(v->fullbooks)free(v->fullbooks);
+
+    // free header, header1, header2
+    if(v->header)free(v->header);
+    if(v->header1)free(v->header1);
+    if(v->header2)free(v->header2);
+
+    memset(v,0,sizeof(vorbis_dsp_state));
+  }
+*/
+}
+}
diff --git a/jorbis/src/com/jcraft/jorbis/EncodeAuxNearestMatch.java b/jorbis/src/com/jcraft/jorbis/EncodeAuxNearestMatch.java
new file mode 100644 (file)
index 0000000..c4b3b06
--- /dev/null
@@ -0,0 +1,36 @@
+/* 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 EncodeAuxNearestMatch{
+  int[] ptr0;
+  int[] ptr1;
+
+  int[] p;         // decision points (each is an entry)
+  int[] q;         // decision points (each is an entry)
+  int   aux;       // number of tree entries
+  int   alloc;       
+}
diff --git a/jorbis/src/com/jcraft/jorbis/EncodeAuxThreshMatch.java b/jorbis/src/com/jcraft/jorbis/EncodeAuxThreshMatch.java
new file mode 100644 (file)
index 0000000..33cb587
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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 EncodeAuxThreshMatch{
+  float[] quantthresh;
+  int[]   quantmap;
+  int     quantvals; 
+  int     threshvals; 
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Floor0.java b/jorbis/src/com/jcraft/jorbis/Floor0.java
new file mode 100644 (file)
index 0000000..3f1d1c3
--- /dev/null
@@ -0,0 +1,352 @@
+/* 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 Floor0 extends FuncFloor{
+
+  void pack(Object i, Buffer opb){
+    InfoFloor0 info=(InfoFloor0)i;
+    opb.write(info.order,8);
+    opb.write(info.rate,16);
+    opb.write(info.barkmap,16);
+    opb.write(info.ampbits,6);
+    opb.write(info.ampdB,8);
+    opb.write(info.numbooks-1,4);
+    for(int j=0;j<info.numbooks;j++)
+      opb.write(info.books[j],8);
+  }
+
+  Object unpack(Info vi , Buffer opb){
+    InfoFloor0 info=new InfoFloor0();
+    info.order=opb.read(8);
+    info.rate=opb.read(16);
+    info.barkmap=opb.read(16);
+    info.ampbits=opb.read(6);
+    info.ampdB=opb.read(8);
+    info.numbooks=opb.read(4)+1;
+  
+    if((info.order<1)||
+       (info.rate<1)||
+       (info.barkmap<1)||
+       (info.numbooks<1)){
+      //free_info(info);
+      return(null);
+    }
+
+    for(int j=0;j<info.numbooks;j++){
+      info.books[j]=opb.read(8);
+      if(info.books[j]<0 || info.books[j]>=vi.books){
+       //free_info(info);
+       return(null);
+      }
+    }
+    return(info);  
+//  err_out:
+//    free_info(info);
+//    return(NULL);
+  }
+  Object look(DspState vd, InfoMode mi, Object i){
+    float scale;
+    Info vi=vd.vi;
+    InfoFloor0 info=(InfoFloor0)i;
+    LookFloor0 look=new LookFloor0();
+    look.m=info.order;
+    look.n=vi.blocksizes[mi.blockflag]/2;
+    look.ln=info.barkmap;
+    look.vi=info;
+    look.lpclook.init(look.ln,look.m);
+
+    // we choose a scaling constant so that:
+    //  floor(bark(rate/2-1)*C)=mapped-1
+    // floor(bark(rate/2)*C)=mapped
+    scale=look.ln/toBARK((float)(info.rate/2.));
+
+    // the mapping from a linear scale to a smaller bark scale is
+    // straightforward.  We do *not* make sure that the linear mapping
+    // does not skip bark-scale bins; the decoder simply skips them and
+    // the encoder may do what it wishes in filling them.  They're
+    // necessary in some mapping combinations to keep the scale spacing
+    // accurate
+    look.linearmap=new int[look.n];
+    for(int j=0;j<look.n;j++){
+      int val=(int)Math.floor(toBARK((float)((info.rate/2.)/look.n*j)) 
+                             *scale); // bark numbers represent band edges
+      if(val>=look.ln)val=look.ln; // guard against the approximation
+      look.linearmap[j]=val;
+    }
+    return look;
+  }
+
+  static float toBARK(float f){
+    return (float)(13.1*Math.atan(.00074*(f))+2.24*Math.atan((f)*(f)*1.85e-8)+1e-4*(f));
+  }
+
+  Object state(Object i){
+    EchstateFloor0 state=new EchstateFloor0();
+    InfoFloor0 info=(InfoFloor0)i;
+
+    // a safe size if usually too big (dim==1)
+    state.codewords=new int[info.order];
+    state.curve=new float[info.barkmap];
+    state.frameno=-1;
+    return(state);
+  }
+  void free_info(Object i){}
+  void free_look(Object i){}
+  void free_state(Object vs){}
+  int forward(Block vb, Object i,  float[] in, float[] out, Object vs){return 0;}
+
+  float[] lsp=null;    
+  int inverse(Block vb, Object i, float[] out){
+    //System.err.println("Floor0.inverse "+i.getClass()+"]");
+    LookFloor0 look=(LookFloor0)i;
+    InfoFloor0 info=look.vi;
+    int ampraw=vb.opb.read(info.ampbits);
+    if(ampraw>0){ // also handles the -1 out of data case
+      int maxval=(1<<info.ampbits)-1;
+      float amp=(float)ampraw/maxval*info.ampdB;
+      int booknum=vb.opb.read(ilog(info.numbooks));
+
+      if(booknum!=-1 && booknum<info.numbooks){
+
+        synchronized(this){ 
+        if(lsp==null||lsp.length<look.m){
+          lsp=new float[look.m];
+        }        
+        else{
+          for(int j=0; j<look.m; j++)lsp[j]=0.f;
+        }
+
+       CodeBook b=vb.vd.fullbooks[info.books[booknum]];
+       float last=0.f;
+
+       //memset(out,0,sizeof(float)*look->m);
+        for(int j=0; j<look.m; j++)out[j]=0.0f;
+
+        for(int j=0;j<look.m;j+=b.dim){
+         if(b.decodevs(lsp, j, vb.opb, 1, -1)==-1){
+           //goto eop;
+           // memset(out,0,sizeof(float)*look->n);
+            for(int k=0; k<look.n; k++)out[k]=0.0f;
+           return(0);
+         }
+       }
+       for(int j=0;j<look.m;){
+         for(int k=0;k<b.dim;k++,j++)lsp[j]+=last;
+         last=lsp[j-1];
+       }
+       // take the coefficients back to a spectral envelope curve
+       /*
+       lsp_to_lpc(out,out,look.m); 
+       lpc_to_curve(out,out,amp,look,"",0);
+       for(int j=0;j<look.n;j++){
+         out[j]=fromdB(out[j]-info.ampdB);
+       }
+       */
+       Lsp.lsp_to_curve(out,look.linearmap,look.n,look.ln,                 
+                        lsp,look.m,amp,info.ampdB);    
+
+       return(1);
+       }
+      }
+    }
+//  eop:
+//    memset(out,0,sizeof(float)*look->n);
+    return(0);
+  }
+
+  Object inverse1(Block vb, Object i, Object memo){
+    //System.err.println("Floor0.inverse "+i.getClass()+"]");
+    LookFloor0 look=(LookFloor0)i;
+    InfoFloor0 info=look.vi;
+    float[] lsp=null;
+    if(memo instanceof float[]){
+      lsp=(float[])memo;
+    }
+
+    int ampraw=vb.opb.read(info.ampbits);
+    if(ampraw>0){ // also handles the -1 out of data case
+      int maxval=(1<<info.ampbits)-1;
+      float amp=(float)ampraw/maxval*info.ampdB;
+      int booknum=vb.opb.read(ilog(info.numbooks));
+
+      if(booknum!=-1 && booknum<info.numbooks){
+       CodeBook b=vb.vd.fullbooks[info.books[booknum]];
+       float last=0.f;
+
+        if(lsp==null||lsp.length<look.m+1){
+          lsp=new float[look.m+1];
+        }        
+        else{
+          for(int j=0; j<lsp.length; j++)lsp[j]=0.f;
+        }
+
+        for(int j=0;j<look.m;j+=b.dim){
+         if(b.decodev_set(lsp, j, vb.opb, b.dim)==-1){
+           //goto eop;
+           return(null);
+         }
+       }
+
+       for(int j=0;j<look.m;){
+         for(int k=0;k<b.dim;k++,j++)lsp[j]+=last;
+         last=lsp[j-1];
+       }
+        lsp[look.m]=amp;
+       return(lsp);
+      }
+    }
+//  eop:
+    return(null);
+  }
+
+  int inverse2(Block vb, Object i, Object memo, float[] out){
+    //System.err.println("Floor0.inverse "+i.getClass()+"]");
+    LookFloor0 look=(LookFloor0)i;
+    InfoFloor0 info=look.vi;
+
+    if(memo!=null){
+      float[] lsp=(float[])memo;
+      float amp=lsp[look.m];
+
+      Lsp.lsp_to_curve(out,look.linearmap,look.n,look.ln,                 
+                      lsp,look.m,amp,info.ampdB);    
+      return(1);
+    }
+//  eop:
+//    memset(out,0,sizeof(float)*look->n);
+    for(int j=0; j<look.n; j++){
+      out[j]=0.f;
+    } 
+    return(0);
+  }
+
+  static float fromdB(float x){
+    return (float)(Math.exp((x)*.11512925));
+  }
+  private static int ilog(int v){
+    int ret=0;
+    while(v!=0){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+  static void lsp_to_lpc(float[] lsp, float[] lpc, int m){ 
+    int i,j,m2=m/2;
+    float[] O=new float[m2];
+    float[] E=new float[m2];
+    float A;
+    float[] Ae=new float[m2+1];
+    float[] Ao=new float[m2+1];
+    float B;
+    float[] Be=new float[m2];
+    float[] Bo=new float[m2];
+    float temp;
+
+    // even/odd roots setup
+    for(i=0;i<m2;i++){
+      O[i]=(float)(-2.*Math.cos(lsp[i*2]));
+      E[i]=(float)(-2.*Math.cos(lsp[i*2+1]));
+    }
+
+    // set up impulse response
+    for(j=0;j<m2;j++){
+      Ae[j]=0.f;
+      Ao[j]=1.f;
+      Be[j]=0.f;
+      Bo[j]=1.f;
+    }
+    Ao[j]=1.f;
+    Ae[j]=1.f;
+
+    // run impulse response
+    for(i=1;i<m+1;i++){
+      A=B=0.f;
+      for(j=0;j<m2;j++){
+       temp=O[j]*Ao[j]+Ae[j];
+       Ae[j]=Ao[j];
+       Ao[j]=A;
+       A+=temp;
+
+       temp=E[j]*Bo[j]+Be[j];
+       Be[j]=Bo[j];
+       Bo[j]=B;
+       B+=temp;
+      }
+      lpc[i-1]=(A+Ao[j]+B-Ae[j])/2;
+      Ao[j]=A;
+      Ae[j]=B;
+    }
+  }
+
+  static void lpc_to_curve(float[] curve, float[] lpc,float amp,
+                          LookFloor0 l, String name, int frameno){
+    // l->m+1 must be less than l->ln, but guard in case we get a bad stream
+    float[] lcurve=new float[Math.max(l.ln*2,l.m*2+2)];
+
+    if(amp==0){
+      //memset(curve,0,sizeof(float)*l->n);
+      for(int j=0; j<l.n; j++)curve[j]=0.0f;
+      return;
+    }
+    l.lpclook.lpc_to_curve(lcurve,lpc,amp);
+
+    for(int i=0;i<l.n;i++)curve[i]=lcurve[l.linearmap[i]];
+  }
+}
+
+class InfoFloor0{
+  int order;
+  int rate;
+  int barkmap;
+
+  int   ampbits;
+  int   ampdB;
+
+  int   numbooks; // <= 16
+  int[]  books=new int[16];
+}
+
+class LookFloor0{
+  int n;
+  int ln;
+  int m;
+  int[] linearmap;
+
+  InfoFloor0 vi;
+  Lpc lpclook=new Lpc();
+}
+
+class EchstateFloor0{
+  int[] codewords;
+  float[] curve;
+  long frameno;
+  long codes;
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Floor1.java b/jorbis/src/com/jcraft/jorbis/Floor1.java
new file mode 100644 (file)
index 0000000..1e52c3e
--- /dev/null
@@ -0,0 +1,653 @@
+/* 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 Floor1 extends FuncFloor{
+  static final int floor1_rangedb=140;
+  static final int VIF_POSIT=63;
+
+  void pack(Object i, Buffer opb){
+    InfoFloor1 info=(InfoFloor1)i;
+
+    int count=0;
+    int rangebits;
+    int maxposit=info.postlist[1];
+    int maxclass=-1;
+
+    /* save out partitions */
+    opb.write(info.partitions,5);          /* only 0 to 31 legal */
+    for(int j=0;j<info.partitions;j++){
+      opb.write(info.partitionclass[j],4); /* only 0 to 15 legal */
+      if(maxclass<info.partitionclass[j])
+        maxclass=info.partitionclass[j];
+    }
+
+    /* save out partition classes */
+    for(int j=0;j<maxclass+1;j++){
+      opb.write(info.class_dim[j]-1,3); /* 1 to 8 */
+      opb.write(info.class_subs[j],2); /* 0 to 3 */
+      if(info.class_subs[j]!=0){
+        opb.write(info.class_book[j],8);
+      }
+      for(int k=0;k<(1<<info.class_subs[j]);k++){
+        opb.write(info.class_subbook[j][k]+1,8);
+      }
+    }
+
+    /* save out the post list */
+    opb.write(info.mult-1,2);     /* only 1,2,3,4 legal now */
+    opb.write(ilog2(maxposit),4);
+    rangebits=ilog2(maxposit);
+
+    for(int j=0,k=0;j<info.partitions;j++){
+      count+=info.class_dim[info.partitionclass[j]];
+      for(;k<count;k++){
+        opb.write(info.postlist[k+2],rangebits);
+      }
+    }
+  }
+
+  Object unpack(Info vi , Buffer opb){
+    int count=0,maxclass=-1,rangebits;
+    InfoFloor1 info=new InfoFloor1();
+
+    /* read partitions */
+    info.partitions=opb.read(5);            /* only 0 to 31 legal */
+    for(int j=0;j<info.partitions;j++){
+      info.partitionclass[j]=opb.read(4); /* only 0 to 15 legal */
+      if(maxclass<info.partitionclass[j])
+        maxclass=info.partitionclass[j];
+    }
+
+    /* read partition classes */
+    for(int j=0;j<maxclass+1;j++){
+      info.class_dim[j]=opb.read(3)+1; /* 1 to 8 */
+      info.class_subs[j]=opb.read(2);  /* 0,1,2,3 bits */
+      if(info.class_subs[j]<0){
+        //goto err_out;
+       info.free();
+        return(null);
+      }
+      if(info.class_subs[j]!=0){
+        info.class_book[j]=opb.read(8);
+      }
+      if(info.class_book[j]<0 || info.class_book[j]>=vi.books){
+        //goto err_out;
+       info.free();
+        return(null);
+      }
+      for(int k=0;k<(1<<info.class_subs[j]);k++){
+        info.class_subbook[j][k]=opb.read(8)-1;
+        if(info.class_subbook[j][k]<-1 || info.class_subbook[j][k]>=vi.books){
+          //goto err_out;
+         info.free();
+          return(null);
+       }
+      }
+    }
+
+    /* read the post list */
+    info.mult=opb.read(2)+1;     /* only 1,2,3,4 legal now */
+    rangebits=opb.read(4);
+
+    for(int j=0,k=0;j<info.partitions;j++){
+      count+=info.class_dim[info.partitionclass[j]];
+      for(;k<count;k++){
+        int t=info.postlist[k+2]=opb.read(rangebits);
+        if(t<0 || t>=(1<<rangebits)){
+          //goto err_out;
+         info.free();
+          return(null);
+       }
+      }
+    }
+    info.postlist[0]=0;
+    info.postlist[1]=1<<rangebits;
+
+    return(info);
+//  err_out:
+//    info.free();
+//    return(null);
+  }
+
+  Object look(DspState vd, InfoMode mi, Object i){
+    int _n=0;
+
+    int[] sortpointer=new int[VIF_POSIT+2];
+
+//    Info vi=vd.vi;
+
+    InfoFloor1 info=(InfoFloor1)i;
+    LookFloor1 look=new LookFloor1();
+    look.vi=info;
+    look.n=info.postlist[1];
+
+    /* we drop each position value in-between already decoded values,
+     and use linear interpolation to predict each new value past the
+     edges.  The positions are read in the order of the position
+     list... we precompute the bounding positions in the lookup.  Of
+     course, the neighbors can change (if a position is declined), but
+     this is an initial mapping */
+
+    for(int j=0;j<info.partitions;j++){
+      _n+=info.class_dim[info.partitionclass[j]];
+    }
+    _n+=2;
+    look.posts=_n;
+
+    /* also store a sorted position index */
+    for(int j=0;j<_n;j++){
+      sortpointer[j]=j;
+    }
+//    qsort(sortpointer,n,sizeof(int),icomp); // !!
+
+    int foo; 
+    for(int j=0; j<_n-1; j++){
+      for(int k=j; k<_n; k++){
+        if(info.postlist[sortpointer[j]]>info.postlist[sortpointer[k]]){
+          foo=sortpointer[k];
+          sortpointer[k]=sortpointer[j];
+          sortpointer[j]=foo;
+       }
+      }
+    }
+
+    /* points from sort order back to range number */
+    for(int j=0;j<_n;j++){
+      look.forward_index[j]=sortpointer[j];
+    }
+    /* points from range order to sorted position */
+    for(int j=0;j<_n;j++){
+      look.reverse_index[look.forward_index[j]]=j;
+    }
+    /* we actually need the post values too */
+    for(int j=0;j<_n;j++){
+      look.sorted_index[j]=info.postlist[look.forward_index[j]];
+    }
+
+
+    /* quantize values to multiplier spec */
+    switch(info.mult){
+    case 1: /* 1024 -> 256 */
+       look.quant_q=256;
+       break;
+    case 2: /* 1024 -> 128 */
+       look.quant_q=128;
+       break;
+    case 3: /* 1024 -> 86 */
+       look.quant_q=86;
+       break;
+    case 4: /* 1024 -> 64 */
+       look.quant_q=64;
+       break;
+    default:
+       look.quant_q=-1;
+    }
+
+    /* discover our neighbors for decode where we don't use fit flags
+       (that would push the neighbors outward) */
+    for(int j=0;j<_n-2;j++){
+      int lo=0;
+      int hi=1;
+      int lx=0;
+      int hx=look.n;
+      int currentx=info.postlist[j+2];
+      for(int k=0;k<j+2;k++){
+        int x=info.postlist[k];
+        if(x>lx && x<currentx){
+          lo=k;
+          lx=x;
+        }
+       if(x<hx && x>currentx){
+         hi=k;
+          hx=x;
+        }
+      }
+      look.loneighbor[j]=lo;
+      look.hineighbor[j]=hi;
+    }
+
+    return look;
+  }
+
+  void free_info(Object i){}
+  void free_look(Object i){}
+  void free_state(Object vs){}
+
+  int forward(Block vb, Object i,  float[] in, float[] out, Object vs){return 0;}
+
+  Object inverse1(Block vb, Object ii, Object memo){
+    //System.err.println("Floor1.inverse "+i.getClass()+"]");
+    LookFloor1 look=(LookFloor1)ii;
+    InfoFloor1 info=look.vi;
+    CodeBook[] books=vb.vd.fullbooks;
+
+    /* unpack wrapped/predicted values from stream */
+    if(vb.opb.read(1)==1){
+      int[] fit_value=null;
+      if(memo instanceof int[]){
+        fit_value=(int[])memo;
+      }
+      if(fit_value==null || fit_value.length<look.posts){
+        fit_value=new int[look.posts];
+      }
+      else{
+        for(int i=0; i<fit_value.length; i++) fit_value[i]=0;
+      }
+
+      fit_value[0]=vb.opb.read(ilog(look.quant_q-1));
+      fit_value[1]=vb.opb.read(ilog(look.quant_q-1));
+
+      /* partition by partition */
+      for(int i=0,j=2;i<info.partitions;i++){
+        int clss=info.partitionclass[i];
+        int cdim=info.class_dim[clss];
+        int csubbits=info.class_subs[clss];
+        int csub=1<<csubbits;
+       int cval=0;
+
+       /* decode the partition's first stage cascade value */
+       if(csubbits!=0){
+          cval=books[info.class_book[clss]].decode(vb.opb);
+
+          if(cval==-1){
+              //goto eop;
+             return(null);
+          }
+       }
+
+       for(int k=0;k<cdim;k++){
+          int book=info.class_subbook[clss][cval&(csub-1)];
+          cval>>>=csubbits;
+          if(book>=0){
+            if((fit_value[j+k]=books[book].decode(vb.opb))==-1){
+              //goto eop;
+              return(null);
+           }
+          }
+          else{
+            fit_value[j+k]=0;
+         }
+       }
+       j+=cdim;
+      }
+
+      /* unwrap positive values and reconsitute via linear interpolation */
+      for(int i=2;i<look.posts;i++){
+        int predicted=render_point(info.postlist[look.loneighbor[i-2]],
+                                  info.postlist[look.hineighbor[i-2]],
+                                  fit_value[look.loneighbor[i-2]],
+                                  fit_value[look.hineighbor[i-2]],
+                                  info.postlist[i]);
+       int hiroom=look.quant_q-predicted;
+       int loroom=predicted;
+       int room=(hiroom<loroom?hiroom:loroom)<<1;
+       int val=fit_value[i];
+
+       if(val!=0){
+          if(val>=room){
+           if(hiroom>loroom){
+             val = val-loroom;
+            }
+            else{
+             val = -1-(val-hiroom);
+            }
+          }
+          else{
+           if((val&1)!=0){
+             val= -((val+1)>>>1);
+            }
+            else{
+             val>>=1;
+            }
+          }
+
+          fit_value[i]=val+predicted;
+         fit_value[look.loneighbor[i-2]]&=0x7fff;
+         fit_value[look.hineighbor[i-2]]&=0x7fff;
+       }
+        else{
+          fit_value[i]=predicted|0x8000;
+       }
+      }
+      return(fit_value);
+    }
+
+//  eop:
+//    return(NULL);
+    return(null);
+  }
+
+  private static int render_point(int x0,int x1,int y0,int y1,int x){
+    y0&=0x7fff; /* mask off flag */
+    y1&=0x7fff;
+    
+    {
+      int dy=y1-y0;
+      int adx=x1-x0;
+      int ady=Math.abs(dy);
+      int err=ady*(x-x0);
+    
+      int off=(int)(err/adx);
+      if(dy<0)return(y0-off);
+      return(y0+off);
+    }
+  }
+
+  int inverse2(Block vb, Object i, Object memo, float[] out){
+    LookFloor1 look=(LookFloor1)i;
+    InfoFloor1 info=look.vi;
+    int n=vb.vd.vi.blocksizes[vb.mode]/2;
+
+    if(memo!=null){
+      /* render the lines */
+      int[] fit_value=(int[] )memo;
+      int hx=0;
+      int lx=0;
+      int ly=fit_value[0]*info.mult;
+      for(int j=1;j<look.posts;j++){
+        int current=look.forward_index[j];
+        int hy=fit_value[current]&0x7fff;
+        if(hy==fit_value[current]){
+         hy*=info.mult;
+          hx=info.postlist[current];
+
+          render_line(lx,hx,ly,hy,out);
+
+          lx=hx;
+          ly=hy;
+       }
+      }
+      for(int j=hx;j<n;j++){
+        out[j]*=out[j-1]; /* be certain */
+      }
+      return(1);
+    }
+    for(int j=0; j<n; j++){
+      out[j]=0.f;
+    } 
+    return(0);
+  }
+
+
+  private static float[] FLOOR_fromdB_LOOKUP={
+      1.0649863e-07F, 1.1341951e-07F, 1.2079015e-07F, 1.2863978e-07F, 
+      1.3699951e-07F, 1.4590251e-07F, 1.5538408e-07F, 1.6548181e-07F, 
+      1.7623575e-07F, 1.8768855e-07F, 1.9988561e-07F, 2.128753e-07F, 
+      2.2670913e-07F, 2.4144197e-07F, 2.5713223e-07F, 2.7384213e-07F, 
+      2.9163793e-07F, 3.1059021e-07F, 3.3077411e-07F, 3.5226968e-07F, 
+      3.7516214e-07F, 3.9954229e-07F, 4.2550680e-07F, 4.5315863e-07F, 
+      4.8260743e-07F, 5.1396998e-07F, 5.4737065e-07F, 5.8294187e-07F, 
+      6.2082472e-07F, 6.6116941e-07F, 7.0413592e-07F, 7.4989464e-07F, 
+      7.9862701e-07F, 8.5052630e-07F, 9.0579828e-07F, 9.6466216e-07F, 
+      1.0273513e-06F, 1.0941144e-06F, 1.1652161e-06F, 1.2409384e-06F, 
+      1.3215816e-06F, 1.4074654e-06F, 1.4989305e-06F, 1.5963394e-06F, 
+      1.7000785e-06F, 1.8105592e-06F, 1.9282195e-06F, 2.0535261e-06F, 
+      2.1869758e-06F, 2.3290978e-06F, 2.4804557e-06F, 2.6416497e-06F, 
+      2.8133190e-06F, 2.9961443e-06F, 3.1908506e-06F, 3.3982101e-06F, 
+      3.6190449e-06F, 3.8542308e-06F, 4.1047004e-06F, 4.3714470e-06F, 
+      4.6555282e-06F, 4.9580707e-06F, 5.2802740e-06F, 5.6234160e-06F, 
+      5.9888572e-06F, 6.3780469e-06F, 6.7925283e-06F, 7.2339451e-06F, 
+      7.7040476e-06F, 8.2047000e-06F, 8.7378876e-06F, 9.3057248e-06F, 
+      9.9104632e-06F, 1.0554501e-05F, 1.1240392e-05F, 1.1970856e-05F, 
+      1.2748789e-05F, 1.3577278e-05F, 1.4459606e-05F, 1.5399272e-05F, 
+      1.6400004e-05F, 1.7465768e-05F, 1.8600792e-05F, 1.9809576e-05F, 
+      2.1096914e-05F, 2.2467911e-05F, 2.3928002e-05F, 2.5482978e-05F, 
+      2.7139006e-05F, 2.8902651e-05F, 3.0780908e-05F, 3.2781225e-05F, 
+      3.4911534e-05F, 3.7180282e-05F, 3.9596466e-05F, 4.2169667e-05F, 
+      4.4910090e-05F, 4.7828601e-05F, 5.0936773e-05F, 5.4246931e-05F, 
+      5.7772202e-05F, 6.1526565e-05F, 6.5524908e-05F, 6.9783085e-05F, 
+      7.4317983e-05F, 7.9147585e-05F, 8.4291040e-05F, 8.9768747e-05F, 
+      9.5602426e-05F, 0.00010181521F, 0.00010843174F, 0.00011547824F, 
+      0.00012298267F, 0.00013097477F, 0.00013948625F, 0.00014855085F, 
+      0.00015820453F, 0.00016848555F, 0.00017943469F, 0.00019109536F, 
+      0.00020351382F, 0.00021673929F, 0.00023082423F, 0.00024582449F, 
+      0.00026179955F, 0.00027881276F, 0.00029693158F, 0.00031622787F, 
+      0.00033677814F, 0.00035866388F, 0.00038197188F, 0.00040679456F, 
+      0.00043323036F, 0.00046138411F, 0.00049136745F, 0.00052329927F, 
+      0.00055730621F, 0.00059352311F, 0.00063209358F, 0.00067317058F, 
+      0.00071691700F, 0.00076350630F, 0.00081312324F, 0.00086596457F, 
+      0.00092223983F, 0.00098217216F, 0.0010459992F, 0.0011139742F, 
+      0.0011863665F, 0.0012634633F, 0.0013455702F, 0.0014330129F, 
+      0.0015261382F, 0.0016253153F, 0.0017309374F, 0.0018434235F, 
+      0.0019632195F, 0.0020908006F, 0.0022266726F, 0.0023713743F, 
+      0.0025254795F, 0.0026895994F, 0.0028643847F, 0.0030505286F, 
+      0.0032487691F, 0.0034598925F, 0.0036847358F, 0.0039241906F, 
+      0.0041792066F, 0.0044507950F, 0.0047400328F, 0.0050480668F, 
+      0.0053761186F, 0.0057254891F, 0.0060975636F, 0.0064938176F, 
+      0.0069158225F, 0.0073652516F, 0.0078438871F, 0.0083536271F, 
+      0.0088964928F, 0.009474637F, 0.010090352F, 0.010746080F, 
+      0.011444421F, 0.012188144F, 0.012980198F, 0.013823725F, 
+      0.014722068F, 0.015678791F, 0.016697687F, 0.017782797F, 
+      0.018938423F, 0.020169149F, 0.021479854F, 0.022875735F, 
+      0.024362330F, 0.025945531F, 0.027631618F, 0.029427276F, 
+      0.031339626F, 0.033376252F, 0.035545228F, 0.037855157F, 
+      0.040315199F, 0.042935108F, 0.045725273F, 0.048696758F, 
+      0.051861348F, 0.055231591F, 0.058820850F, 0.062643361F, 
+      0.066714279F, 0.071049749F, 0.075666962F, 0.080584227F, 
+      0.085821044F, 0.091398179F, 0.097337747F, 0.10366330F, 
+      0.11039993F, 0.11757434F, 0.12521498F, 0.13335215F, 
+      0.14201813F, 0.15124727F, 0.16107617F, 0.17154380F, 
+      0.18269168F, 0.19456402F, 0.20720788F, 0.22067342F, 
+      0.23501402F, 0.25028656F, 0.26655159F, 0.28387361F, 
+      0.30232132F, 0.32196786F, 0.34289114F, 0.36517414F, 
+      0.38890521F, 0.41417847F, 0.44109412F, 0.46975890F, 
+      0.50028648F, 0.53279791F, 0.56742212F, 0.60429640F, 
+      0.64356699F, 0.68538959F, 0.72993007F, 0.77736504F, 
+      0.82788260F, 0.88168307F, 0.9389798F, 1.F 
+  };
+
+  private static void render_line(int x0, int x1,int y0,int y1,float[] d){
+    int dy=y1-y0;
+    int adx=x1-x0;
+    int ady=Math.abs(dy);
+    int base=dy/adx;
+    int sy=(dy<0?base-1:base+1);
+    int x=x0;
+    int y=y0;
+    int err=0;
+
+    ady-=Math.abs(base*adx);
+
+    d[x]*=FLOOR_fromdB_LOOKUP[y];
+    while(++x<x1){
+      err=err+ady;
+      if(err>=adx){
+        err-=adx;
+        y+=sy;
+      }
+      else{
+        y+=base;
+      }
+      d[x]*=FLOOR_fromdB_LOOKUP[y];
+    }
+  }
+
+  static int ilog(int v){
+    int ret=0;
+    while(v!=0){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+  private static int ilog2(int v){
+    int ret=0;
+    while(v>1){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+}
+
+class InfoFloor1{
+  static final int VIF_POSIT=63;
+  static final int VIF_CLASS=16;
+  static final int VIF_PARTS=31;
+
+  int   partitions;                        /* 0 to 31 */
+  int[] partitionclass=new int[VIF_PARTS]; /* 0 to 15 */
+
+  int[] class_dim=new int[VIF_CLASS];        /* 1 to 8 */
+  int[] class_subs=new int[VIF_CLASS];       /* 0,1,2,3 (bits: 1<<n poss) */
+  int[] class_book=new int[VIF_CLASS];       /* subs ^ dim entries */
+  int[][] class_subbook=new int[VIF_CLASS][]; /* [VIF_CLASS][subs] */
+
+
+  int mult;                               /* 1 2 3 or 4 */
+  int[] postlist=new int[VIF_POSIT+2];    /* first two implicit */
+
+
+  /* encode side analysis parameters */
+  float maxover;
+  float maxunder;
+  float maxerr;
+
+  int   twofitminsize;
+  int   twofitminused;
+  int   twofitweight;
+  float twofitatten;
+  int   unusedminsize;
+  int   unusedmin_n;
+
+  int   n;
+
+  InfoFloor1(){
+    for(int i=0; i<class_subbook.length; i++){
+      class_subbook[i]=new int[8];      
+    }
+  }
+
+  void free(){
+    partitionclass=null;
+    class_dim=null;
+    class_subs=null;
+    class_book=null;
+    class_subbook=null;
+    postlist=null;
+  }
+
+  Object copy_info(){
+    InfoFloor1 info=this;
+    InfoFloor1 ret=new InfoFloor1();
+
+    ret.partitions=info.partitions;
+    System.arraycopy(info.partitionclass, 0, ret.partitionclass, 0, VIF_PARTS);
+    System.arraycopy(info.class_dim, 0, ret.class_dim, 0, VIF_CLASS);
+    System.arraycopy(info.class_subs, 0, ret.class_subs, 0, VIF_CLASS);
+    System.arraycopy(info.class_book, 0, ret.class_book, 0, VIF_CLASS);
+
+    for(int j=0; j<VIF_CLASS; j++){
+      System.arraycopy(info.class_subbook[j], 0, 
+                      ret.class_subbook[j], 0, 8);
+    }
+
+    ret.mult=info.mult;
+    System.arraycopy(info.postlist, 0, ret.postlist, 0, VIF_POSIT+2);
+
+    ret.maxover=info.maxover;
+    ret.maxunder=info.maxunder;
+    ret.maxerr=info.maxerr;
+
+    ret.twofitminsize=info.twofitminsize;
+    ret.twofitminused=info.twofitminused;
+    ret.twofitweight=info.twofitweight;
+    ret.twofitatten=info.twofitatten;
+    ret.unusedminsize=info.unusedminsize;
+    ret.unusedmin_n=info.unusedmin_n;
+
+    ret.n=info.n;
+
+    return(ret);
+  }
+
+}
+
+class LookFloor1{
+  static final int VIF_POSIT=63;
+
+  int[] sorted_index=new int[VIF_POSIT+2];
+  int[] forward_index=new int[VIF_POSIT+2];
+  int[] reverse_index=new int[VIF_POSIT+2];
+  int[] hineighbor=new int[VIF_POSIT];
+  int[] loneighbor=new int[VIF_POSIT];
+  int posts;
+
+  int n;
+  int quant_q;
+  InfoFloor1 vi;
+
+  int phrasebits;
+  int postbits;
+  int frames;
+
+  void free(){
+
+/*
+    System.out.println("floor 1 bit usage "+
+                       (float)(phrasebits/frames)
+                       +":"+
+                       (float)(postbits/frames)
+                       +"("+
+                       (float)((postbits+phrasebits)/frames)
+                       +" total)"
+            
+*/
+
+    sorted_index=null;
+    forward_index=null;
+    reverse_index=null;
+    hineighbor=null;
+    loneighbor=null;
+  }
+}
+
+class Lsfit_acc{
+  long x0;
+  long x1;
+
+  long xa;
+  long ya;
+  long x2a;
+  long y2a;
+  long xya;
+  long n;
+  long an;
+  long un;
+  long edgey0;
+  long edgey1;
+} 
+
+class EchstateFloor1{
+  int[] codewords;
+  float[] curve;
+  long frameno;
+  long codes;
+}
diff --git a/jorbis/src/com/jcraft/jorbis/FuncFloor.java b/jorbis/src/com/jcraft/jorbis/FuncFloor.java
new file mode 100644 (file)
index 0000000..f438d0f
--- /dev/null
@@ -0,0 +1,45 @@
+/* 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.*;
+
+abstract class FuncFloor{
+//  public static FuncFloor[] floor_P={new Floor0()};
+  public static FuncFloor[] floor_P={new Floor0(),new Floor1()};
+
+  abstract void pack(Object i, Buffer opb);
+  abstract Object unpack(Info vi, Buffer opb);
+  abstract Object look(DspState vd, InfoMode mi, Object i);
+//  abstract Object state(Object i);
+  abstract void free_info(Object i);
+  abstract void free_look(Object i);
+  abstract void free_state(Object vs);
+  abstract int forward(Block vb, Object i, float[] in, float[] out, Object vs);
+//  abstract int inverse(Block vb, Object i, float[] out);
+  abstract Object inverse1(Block vb, Object i, Object memo);
+  abstract int inverse2(Block vb, Object i, Object memo, float[] out);
+}
diff --git a/jorbis/src/com/jcraft/jorbis/FuncMapping.java b/jorbis/src/com/jcraft/jorbis/FuncMapping.java
new file mode 100644 (file)
index 0000000..c8ecf75
--- /dev/null
@@ -0,0 +1,40 @@
+/* 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.*;
+
+abstract class FuncMapping{
+  public static FuncMapping[] mapping_P={new Mapping0()};
+
+  abstract void pack(Info info , Object imap, Buffer buffer);
+  abstract Object unpack(Info info , Buffer buffer);
+  abstract Object look(DspState vd, InfoMode vm, Object m);
+  abstract void free_info(Object imap);
+  abstract void free_look(Object imap);
+//  abstract int forward(Block vd, Object lm);
+  abstract int inverse(Block vd, Object lm);
+}
diff --git a/jorbis/src/com/jcraft/jorbis/FuncResidue.java b/jorbis/src/com/jcraft/jorbis/FuncResidue.java
new file mode 100644 (file)
index 0000000..4cbf6a1
--- /dev/null
@@ -0,0 +1,43 @@
+/* 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.*;
+
+abstract class FuncResidue{
+  public static FuncResidue[] residue_P={new Residue0(),
+                                        new Residue1(),
+                                        new Residue2()};
+
+  abstract void pack(Object vr, Buffer opb);
+  abstract Object unpack(Info vi, Buffer opb);
+  abstract Object look(DspState vd, InfoMode vm, Object vr);
+  abstract void free_info(Object i);
+  abstract void free_look(Object i);
+  abstract int forward(Block vb,Object vl, float[][] in, int ch);
+//  abstract int inverse(Block vb, Object vl, float[][] in, int ch);
+abstract int inverse(Block vb, Object vl, float[][] in, int[] nonzero,int ch);
+}
diff --git a/jorbis/src/com/jcraft/jorbis/FuncTime.java b/jorbis/src/com/jcraft/jorbis/FuncTime.java
new file mode 100644 (file)
index 0000000..b3cd080
--- /dev/null
@@ -0,0 +1,40 @@
+/* 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.*;
+
+abstract class FuncTime{
+  public static FuncTime[] time_P={new Time0()};
+
+  abstract void pack(Object i, Buffer opb);
+  abstract Object unpack(Info vi , Buffer opb);
+  abstract Object look(DspState vd, InfoMode vm, Object i);
+  abstract void free_info(Object i);
+  abstract void free_look(Object i);
+  abstract int forward(Block vb, Object i);
+  abstract int inverse(Block vb, Object i, float[] in, float[] out);
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Info.java b/jorbis/src/com/jcraft/jorbis/Info.java
new file mode 100644 (file)
index 0000000..dffd4d9
--- /dev/null
@@ -0,0 +1,516 @@
+/* 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 Info{
+  private static final int OV_EBADPACKET=-136;
+  private static final int OV_ENOTAUDIO=-135;
+
+  private static byte[] _vorbis="vorbis".getBytes();
+  private static final int VI_TIMEB=1;
+//  private static final int VI_FLOORB=1;
+  private static final int VI_FLOORB=2;
+//  private static final int VI_RESB=1;
+  private static final int VI_RESB=3;
+  private static final int VI_MAPB=1;
+  private static final int VI_WINDOWB=1;
+
+  public int version;
+  public int channels;
+  public int rate;
+
+  // The below bitrate declarations are *hints*.
+  // Combinations of the three values carry the following implications:
+  //     
+  // all three set to the same value: 
+  // implies a fixed rate bitstream
+  // only nominal set: 
+  // implies a VBR stream that averages the nominal bitrate.  No hard 
+  // upper/lower limit
+  // upper and or lower set: 
+  // implies a VBR bitstream that obeys the bitrate limits. nominal 
+  // may also be set to give a nominal rate.
+  // none set:
+  //  the coder does not care to speculate.
+
+  int bitrate_upper;
+  int bitrate_nominal;
+  int bitrate_lower;
+  
+  // Vorbis supports only short and long blocks, but allows the
+  // encoder to choose the sizes
+
+  int[] blocksizes=new int[2];
+
+  // modes are the primary means of supporting on-the-fly different
+  // blocksizes, different channel mappings (LR or mid-side),
+  // different residue backends, etc.  Each mode consists of a
+  // blocksize flag and a mapping (along with the mapping setup
+
+  int modes;
+  int maps;
+  int times;
+  int floors;
+  int residues;
+  int books;
+  int psys;     // encode only
+
+  InfoMode[] mode_param=null;
+
+  int[] map_type=null;
+  Object[] map_param=null;
+
+  int[] time_type=null;
+  Object[] time_param=null;
+
+  int[] floor_type=null;
+  Object[] floor_param=null;
+
+  int[] residue_type=null;
+  Object[] residue_param=null;
+
+  StaticCodeBook[] book_param=null;
+
+  PsyInfo[] psy_param=new PsyInfo[64]; // encode only
+  
+  // for block long/sort tuning; encode only
+  int envelopesa;
+  float preecho_thresh;
+  float preecho_clamp;
+
+  // used by synthesis, which has a full, alloced vi
+  public void init(){
+    rate=0;
+    //memset(vi,0,sizeof(vorbis_info));
+  }
+
+  public void clear(){
+    for(int i=0;i<modes;i++){ mode_param[i]=null; }
+    mode_param=null;
+
+    for(int i=0;i<maps;i++){ // unpack does the range checking
+      FuncMapping.mapping_P[map_type[i]].free_info(map_param[i]);
+    }
+    map_param=null;
+
+    for(int i=0;i<times;i++){ // unpack does the range checking
+      FuncTime.time_P[time_type[i]].free_info(time_param[i]);
+    }
+    time_param=null;
+
+    for(int i=0;i<floors;i++){ // unpack does the range checking
+      FuncFloor.floor_P[floor_type[i]].free_info(floor_param[i]);
+    }
+    floor_param=null;
+
+    for(int i=0;i<residues;i++){ // unpack does the range checking
+      FuncResidue.residue_P[residue_type[i]].free_info(residue_param[i]);
+    }
+    residue_param=null;
+
+    // the static codebooks *are* freed if you call info_clear, because
+    // decode side does alloc a 'static' codebook. Calling clear on the
+    // full codebook does not clear the static codebook (that's our
+    // responsibility)
+    for(int i=0;i<books;i++){
+      // just in case the decoder pre-cleared to save space
+      if(book_param[i]!=null){
+       book_param[i].clear();
+       book_param[i]=null;
+      }
+    }
+    //if(vi->book_param)free(vi->book_param);
+    book_param=null;
+
+    for(int i=0;i<psys;i++){
+      psy_param[i].free();
+    }
+    //if(vi->psy_param)free(vi->psy_param);
+    //memset(vi,0,sizeof(vorbis_info));
+  }
+
+  // Header packing/unpacking
+  int unpack_info(Buffer opb){
+    version=opb.read(32);
+    if(version!=0)return(-1);
+
+    channels=opb.read(8);
+    rate=opb.read(32);
+
+    bitrate_upper=opb.read(32);
+    bitrate_nominal=opb.read(32);
+    bitrate_lower=opb.read(32);
+
+    blocksizes[0]=1<<opb.read(4);
+    blocksizes[1]=1<<opb.read(4);
+  
+    if((rate<1) ||
+       (channels<1)||
+       (blocksizes[0]<8)||
+       (blocksizes[1]<blocksizes[0]) ||
+       (opb.read(1)!=1)){
+      //goto err_out; // EOP check
+      clear();
+      return(-1);
+    }
+    return(0);
+    // err_out:
+    // vorbis_info_clear(vi);
+    // return(-1);
+  }
+
+  // all of the real encoding details are here.  The modes, books,
+  // everything
+  int unpack_books(Buffer opb){
+
+    //d* codebooks
+    books=opb.read(8)+1;
+
+    if(book_param==null || book_param.length!=books)
+      book_param=new StaticCodeBook[books];
+    for(int i=0;i<books;i++){
+      book_param[i]=new StaticCodeBook();
+      if(book_param[i].unpack(opb)!=0){
+       //goto err_out;
+       clear();
+       return(-1);
+      }
+    }
+
+    // time backend settings
+    times=opb.read(6)+1;
+    if(time_type==null || time_type.length!=times) time_type=new int[times];
+    if(time_param==null || time_param.length!=times)
+      time_param=new Object[times];
+    for(int i=0;i<times;i++){
+      time_type[i]=opb.read(16);
+      if(time_type[i]<0 || time_type[i]>=VI_TIMEB){
+       //goto err_out;
+       clear();
+       return(-1);
+      }
+      time_param[i]=FuncTime.time_P[time_type[i]].unpack(this, opb);
+      if(time_param[i]==null){
+       //goto err_out;
+       clear();
+       return(-1);
+      }
+    }
+
+    // floor backend settings
+    floors=opb.read(6)+1;
+    if(floor_type==null || floor_type.length!=floors)
+      floor_type=new int[floors];
+    if(floor_param==null || floor_param.length!=floors)
+      floor_param=new Object[floors];
+
+    for(int i=0;i<floors;i++){
+      floor_type[i]=opb.read(16);
+      if(floor_type[i]<0 || floor_type[i]>=VI_FLOORB){
+       //goto err_out;
+       clear();
+       return(-1);
+      }
+
+      floor_param[i]=FuncFloor.floor_P[floor_type[i]].unpack(this,opb);
+      if(floor_param[i]==null){
+       //goto err_out;
+       clear();
+       return(-1);
+      }
+    }
+
+    // residue backend settings
+    residues=opb.read(6)+1;
+
+    if(residue_type==null || residue_type.length!=residues)
+      residue_type=new int[residues];
+
+    if(residue_param==null || residue_param.length!=residues)
+      residue_param=new Object[residues];
+
+    for(int i=0;i<residues;i++){
+      residue_type[i]=opb.read(16);
+      if(residue_type[i]<0 || residue_type[i]>=VI_RESB){
+//     goto err_out;
+       clear();
+       return(-1);
+      }
+      residue_param[i]=FuncResidue.residue_P[residue_type[i]].unpack(this,opb);
+      if(residue_param[i]==null){
+//     goto err_out;
+       clear();
+       return(-1);
+      }
+    }
+
+    // map backend settings
+    maps=opb.read(6)+1;
+    if(map_type==null || map_type.length!=maps)  map_type=new int[maps];
+    if(map_param==null || map_param.length!=maps)  map_param=new Object[maps];
+    for(int i=0;i<maps;i++){
+      map_type[i]=opb.read(16);
+      if(map_type[i]<0 || map_type[i]>=VI_MAPB){
+//     goto err_out;
+       clear();
+       return(-1);
+      }
+      map_param[i]=FuncMapping.mapping_P[map_type[i]].unpack(this,opb);
+      if(map_param[i]==null){
+//    goto err_out;
+       clear();
+       return(-1);
+      }
+    }
+
+  // mode settings
+    modes=opb.read(6)+1;
+    if(mode_param==null || mode_param.length!=modes)
+      mode_param=new InfoMode[modes];
+    for(int i=0;i<modes;i++){
+      mode_param[i]=new InfoMode();
+      mode_param[i].blockflag=opb.read(1);
+      mode_param[i].windowtype=opb.read(16);
+      mode_param[i].transformtype=opb.read(16);
+      mode_param[i].mapping=opb.read(8);
+
+      if((mode_param[i].windowtype>=VI_WINDOWB)||
+        (mode_param[i].transformtype>=VI_WINDOWB)||
+        (mode_param[i].mapping>=maps)){
+//      goto err_out;
+       clear();
+       return(-1);
+      }
+    }
+
+    if(opb.read(1)!=1){
+    //goto err_out; // top level EOP check
+      clear();
+      return(-1);
+    }
+
+    return(0);
+// err_out:
+//  vorbis_info_clear(vi);
+//  return(-1);
+  }
+
+  // The Vorbis header is in three packets; the initial small packet in
+  // the first page that identifies basic parameters, a second packet
+  // with bitstream comments and a third packet that holds the
+  // codebook.
+
+  public int synthesis_headerin(Comment vc, Packet op){
+    Buffer opb=new Buffer();
+
+    if(op!=null){  
+      opb.readinit(op.packet_base, op.packet, op.bytes);
+
+      // Which of the three types of header is this?
+      // Also verify header-ness, vorbis
+      {
+        byte[] buffer=new byte[6];
+        int packtype=opb.read(8);
+        //memset(buffer,0,6);
+        opb.read(buffer,6);
+        if(buffer[0]!='v' || buffer[1]!='o' || buffer[2]!='r' ||
+           buffer[3]!='b' || buffer[4]!='i' || buffer[5]!='s'){
+       // not a vorbis header
+         return(-1);
+        }
+        switch(packtype){
+        case 0x01: // least significant *bit* is read first
+          if(op.b_o_s==0){
+           // Not the initial packet
+           return(-1);
+         }
+         if(rate!=0){
+           // previously initialized info header
+           return(-1);
+         }
+         return(unpack_info(opb));
+        case 0x03: // least significant *bit* is read first
+         if(rate==0){
+           // um... we didn't get the initial header
+           return(-1);
+         }
+         return(vc.unpack(opb));
+        case 0x05: // least significant *bit* is read first
+         if(rate==0 || vc.vendor==null){
+           // um... we didn;t get the initial header or comments yet
+            return(-1);
+         }
+         return(unpack_books(opb));
+        default:
+         // Not a valid vorbis header type
+         //return(-1);
+         break;
+        }
+      }
+    }
+    return(-1);
+  }
+
+  // pack side
+  int pack_info(Buffer opb){
+    // preamble
+    opb.write(0x01,8);
+    opb.write(_vorbis);
+
+    // basic information about the stream
+    opb.write(0x00,32);
+    opb.write(channels,8);
+    opb.write(rate,32);
+
+    opb.write(bitrate_upper,32);
+    opb.write(bitrate_nominal,32);
+    opb.write(bitrate_lower,32);
+
+    opb.write(ilog2(blocksizes[0]),4);
+    opb.write(ilog2(blocksizes[1]),4);
+    opb.write(1,1);
+    return(0);
+  }
+
+  int pack_books(Buffer opb){
+    opb.write(0x05,8);
+    opb.write(_vorbis);
+
+    // books
+    opb.write(books-1,8);
+    for(int i=0;i<books;i++){
+      if(book_param[i].pack(opb)!=0){
+       //goto err_out;
+       return(-1);
+      }
+    }
+
+    // times
+    opb.write(times-1,6);
+    for(int i=0;i<times;i++){
+      opb.write(time_type[i],16);
+      FuncTime.time_P[time_type[i]].pack(this.time_param[i],opb);
+    }
+
+    // floors
+    opb.write(floors-1,6);
+    for(int i=0;i<floors;i++){
+      opb.write(floor_type[i],16);
+      FuncFloor.floor_P[floor_type[i]].pack(floor_param[i],opb);
+    }
+
+    // residues
+    opb.write(residues-1,6);
+    for(int i=0;i<residues;i++){
+      opb.write(residue_type[i],16);
+      FuncResidue.residue_P[residue_type[i]].pack(residue_param[i],opb);
+    }
+    
+    // maps
+    opb.write(maps-1,6);
+    for(int i=0;i<maps;i++){
+      opb.write(map_type[i],16);
+      FuncMapping.mapping_P[map_type[i]].pack(this,map_param[i],opb);
+    }
+
+    // modes
+    opb.write(modes-1,6);
+    for(int i=0;i<modes;i++){
+      opb.write(mode_param[i].blockflag,1);
+      opb.write(mode_param[i].windowtype,16);
+      opb.write(mode_param[i].transformtype,16);
+      opb.write(mode_param[i].mapping,8);
+    }
+    opb.write(1,1);
+    return(0);
+  //err_out:
+    //return(-1);
+  } 
+
+//  static void v_writestring(Buffer o, byte[] s){
+//    int i=0;
+//    while(s[i]!=0){
+//      o.write(s[i++],8);
+//    }
+//  }
+
+//  static void v_readstring(Buffer o, byte[] buf, int bytes){
+//    int i=0
+//    while(bytes--!=0){
+//      buf[i++]=o.read(8);
+//    }
+//  }
+
+//  private Buffer opb_blocksize=new Buffer();
+  public int blocksize(Packet op){
+    //codec_setup_info     *ci=vi->codec_setup;
+    Buffer opb=new Buffer();
+//    synchronized(opb_blocksize){
+    int mode;
+    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(OV_ENOTAUDIO);
+    }
+    {  
+      int modebits=0;
+      int v=modes;
+      while(v>1){
+        modebits++;
+        v>>>=1;
+      }
+
+    /* read our mode and pre/post windowsize */
+      mode=opb.read(modebits);
+    }
+    if(mode==-1)return(OV_EBADPACKET);
+    return(blocksizes[mode_param[mode].blockflag]);
+//    }
+  }
+
+  private static int ilog2(int v){
+    int ret=0;
+    while(v>1){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+  public String toString(){
+    return "version:"+new Integer(version)+
+           ", channels:"+new Integer(channels)+
+           ", rate:"+new Integer(rate)+
+           ", bitrate:"+new Integer(bitrate_upper)+","+
+                        new Integer(bitrate_nominal)+","+
+                       new Integer(bitrate_lower);
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/InfoMode.java b/jorbis/src/com/jcraft/jorbis/InfoMode.java
new file mode 100644 (file)
index 0000000..b570aa5
--- /dev/null
@@ -0,0 +1,33 @@
+/* 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 InfoMode{
+  int blockflag;
+  int windowtype;
+  int transformtype;
+  int mapping;
+}
diff --git a/jorbis/src/com/jcraft/jorbis/JOrbisException.java b/jorbis/src/com/jcraft/jorbis/JOrbisException.java
new file mode 100644 (file)
index 0000000..ce09d4f
--- /dev/null
@@ -0,0 +1,35 @@
+/* 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;
+
+public class JOrbisException extends Exception {
+  public JOrbisException () {
+    super();
+  }
+  public JOrbisException (String s) {
+    super ("JOrbis: "+s);
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Lookup.java b/jorbis/src/com/jcraft/jorbis/Lookup.java
new file mode 100644 (file)
index 0000000..fb7651a
--- /dev/null
@@ -0,0 +1,154 @@
+/* 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 Lookup{
+  static final int COS_LOOKUP_SZ=128;
+  static final float[] COS_LOOKUP={
+       +1.0000000000000f,+0.9996988186962f,+0.9987954562052f,+0.9972904566787f,
+       +0.9951847266722f,+0.9924795345987f,+0.9891765099648f,+0.9852776423889f,
+       +0.9807852804032f,+0.9757021300385f,+0.9700312531945f,+0.9637760657954f,
+       +0.9569403357322f,+0.9495281805930f,+0.9415440651830f,+0.9329927988347f,
+       +0.9238795325113f,+0.9142097557035f,+0.9039892931234f,+0.8932243011955f,
+       +0.8819212643484f,+0.8700869911087f,+0.8577286100003f,+0.8448535652497f,
+       +0.8314696123025f,+0.8175848131516f,+0.8032075314806f,+0.7883464276266f,
+       +0.7730104533627f,+0.7572088465065f,+0.7409511253550f,+0.7242470829515f,
+       +0.7071067811865f,+0.6895405447371f,+0.6715589548470f,+0.6531728429538f,
+       +0.6343932841636f,+0.6152315905806f,+0.5956993044924f,+0.5758081914178f,
+       +0.5555702330196f,+0.5349976198871f,+0.5141027441932f,+0.4928981922298f,
+       +0.4713967368260f,+0.4496113296546f,+0.4275550934303f,+0.4052413140050f,
+       +0.3826834323651f,+0.3598950365350f,+0.3368898533922f,+0.3136817403989f,
+       +0.2902846772545f,+0.2667127574749f,+0.2429801799033f,+0.2191012401569f,
+       +0.1950903220161f,+0.1709618887603f,+0.1467304744554f,+0.1224106751992f,
+       +0.0980171403296f,+0.0735645635997f,+0.0490676743274f,+0.0245412285229f,
+       +0.0000000000000f,-0.0245412285229f,-0.0490676743274f,-0.0735645635997f,
+       -0.0980171403296f,-0.1224106751992f,-0.1467304744554f,-0.1709618887603f,
+       -0.1950903220161f,-0.2191012401569f,-0.2429801799033f,-0.2667127574749f,
+       -0.2902846772545f,-0.3136817403989f,-0.3368898533922f,-0.3598950365350f,
+       -0.3826834323651f,-0.4052413140050f,-0.4275550934303f,-0.4496113296546f,
+       -0.4713967368260f,-0.4928981922298f,-0.5141027441932f,-0.5349976198871f,
+       -0.5555702330196f,-0.5758081914178f,-0.5956993044924f,-0.6152315905806f,
+       -0.6343932841636f,-0.6531728429538f,-0.6715589548470f,-0.6895405447371f,
+       -0.7071067811865f,-0.7242470829515f,-0.7409511253550f,-0.7572088465065f,
+       -0.7730104533627f,-0.7883464276266f,-0.8032075314806f,-0.8175848131516f,
+       -0.8314696123025f,-0.8448535652497f,-0.8577286100003f,-0.8700869911087f,
+       -0.8819212643484f,-0.8932243011955f,-0.9039892931234f,-0.9142097557035f,
+       -0.9238795325113f,-0.9329927988347f,-0.9415440651830f,-0.9495281805930f,
+       -0.9569403357322f,-0.9637760657954f,-0.9700312531945f,-0.9757021300385f,
+       -0.9807852804032f,-0.9852776423889f,-0.9891765099648f,-0.9924795345987f,
+       -0.9951847266722f,-0.9972904566787f,-0.9987954562052f,-0.9996988186962f,
+       -1.0000000000000f,
+  };
+  /* interpolated lookup based cos function, domain 0 to PI only */
+  static float coslook(float a){
+    double d=a*(.31830989*(float)COS_LOOKUP_SZ);
+    int i=(int)d;
+    return COS_LOOKUP[i]+ ((float)(d-i))*(COS_LOOKUP[i+1]-COS_LOOKUP[i]);
+  }     
+
+  static final int INVSQ_LOOKUP_SZ=32;
+  static final float[] INVSQ_LOOKUP={
+       1.414213562373f,1.392621247646f,1.371988681140f,1.352246807566f,
+       1.333333333333f,1.315191898443f,1.297771369046f,1.281025230441f,
+       1.264911064067f,1.249390095109f,1.234426799697f,1.219988562661f,
+       1.206045378311f,1.192569588000f,1.179535649239f,1.166919931983f,
+       1.154700538379f,1.142857142857f,1.131370849898f,1.120224067222f,
+       1.109400392450f,1.098884511590f,1.088662107904f,1.078719779941f,
+       1.069044967650f,1.059625885652f,1.050451462878f,1.041511287847f,
+       1.032795558989f,1.024295039463f,1.016001016002f,1.007905261358f,
+       1.000000000000f,
+  };
+  /* interpolated 1./sqrt(p) where .5 <= p < 1. */
+  static float invsqlook(float a){
+// System.out.println(a);
+    double d=a*(2.f*(float)INVSQ_LOOKUP_SZ)-(float)INVSQ_LOOKUP_SZ;
+    int i=(int)d;
+    return INVSQ_LOOKUP[i]+ ((float)(d-i))*(INVSQ_LOOKUP[i+1]-INVSQ_LOOKUP[i]);
+  }
+
+  static final int INVSQ2EXP_LOOKUP_MIN=-32;
+  static final int INVSQ2EXP_LOOKUP_MAX=32;
+  static final float[] INVSQ2EXP_LOOKUP={
+                65536.f,    46340.95001f,         32768.f,    23170.47501f,
+                16384.f,     11585.2375f,          8192.f,    5792.618751f,
+                 4096.f,    2896.309376f,          2048.f,    1448.154688f,
+                 1024.f,    724.0773439f,           512.f,     362.038672f,
+                  256.f,     181.019336f,           128.f,    90.50966799f,
+                   64.f,      45.254834f,            32.f,      22.627417f,
+                   16.f,     11.3137085f,             8.f,    5.656854249f,
+                    4.f,    2.828427125f,             2.f,    1.414213562f,
+                    1.f,   0.7071067812f,            0.5f,   0.3535533906f,
+                  0.25f,   0.1767766953f,          0.125f,  0.08838834765f,
+                0.0625f,  0.04419417382f,        0.03125f,  0.02209708691f,
+              0.015625f,  0.01104854346f,      0.0078125f, 0.005524271728f,
+            0.00390625f, 0.002762135864f,    0.001953125f, 0.001381067932f,
+          0.0009765625f, 0.000690533966f,  0.00048828125f, 0.000345266983f,
+        0.000244140625f,0.0001726334915f,0.0001220703125f,8.631674575e-05f,
+       6.103515625e-05f,4.315837288e-05f,3.051757812e-05f,2.157918644e-05f,
+       1.525878906e-05f,
+  };
+  /* interpolated 1./sqrt(p) where .5 <= p < 1. */
+  static float invsq2explook(int a){
+    return INVSQ2EXP_LOOKUP[a-INVSQ2EXP_LOOKUP_MIN];
+  }
+
+  static final int FROMdB_LOOKUP_SZ=35;
+  static final int FROMdB2_LOOKUP_SZ=32;
+  static final int FROMdB_SHIFT=5;
+  static final int FROMdB2_SHIFT=3;
+  static final int FROMdB2_MASK=31;
+  static final float[] FROMdB_LOOKUP={
+                    1.f,   0.6309573445f,   0.3981071706f,   0.2511886432f,
+          0.1584893192f,            0.1f,  0.06309573445f,  0.03981071706f,
+         0.02511886432f,  0.01584893192f,           0.01f, 0.006309573445f,
+        0.003981071706f, 0.002511886432f, 0.001584893192f,          0.001f,
+       0.0006309573445f,0.0003981071706f,0.0002511886432f,0.0001584893192f,
+                0.0001f,6.309573445e-05f,3.981071706e-05f,2.511886432e-05f,
+       1.584893192e-05f,          1e-05f,6.309573445e-06f,3.981071706e-06f,
+       2.511886432e-06f,1.584893192e-06f,          1e-06f,6.309573445e-07f,
+       3.981071706e-07f,2.511886432e-07f,1.584893192e-07f,
+  };
+  static final float[] FROMdB2_LOOKUP={
+          0.9928302478f,   0.9786445908f,   0.9646616199f,   0.9508784391f,
+          0.9372921937f,     0.92390007f,   0.9106992942f,   0.8976871324f,
+          0.8848608897f,   0.8722179097f,   0.8597555737f,   0.8474713009f,
+           0.835362547f,   0.8234268041f,   0.8116616003f,   0.8000644989f,
+          0.7886330981f,   0.7773650302f,   0.7662579617f,    0.755309592f,
+          0.7445176537f,   0.7338799116f,   0.7233941627f,   0.7130582353f,
+          0.7028699885f,   0.6928273125f,   0.6829281272f,   0.6731703824f,
+          0.6635520573f,   0.6540711597f,   0.6447257262f,   0.6355138211f,
+  };
+  /* interpolated lookup based fromdB function, domain -140dB to 0dB only */
+  static float fromdBlook(float a){
+    int i=(int)(a*((float)(-(1<<FROMdB2_SHIFT))));
+    return (i<0)?1.f:
+      ((i>=(FROMdB_LOOKUP_SZ<<FROMdB_SHIFT))?0.f:
+       FROMdB_LOOKUP[i>>>FROMdB_SHIFT]*FROMdB2_LOOKUP[i&FROMdB2_MASK]);
+  }
+
+}
+
+
diff --git a/jorbis/src/com/jcraft/jorbis/Lpc.java b/jorbis/src/com/jcraft/jorbis/Lpc.java
new file mode 100644 (file)
index 0000000..452ed86
--- /dev/null
@@ -0,0 +1,254 @@
+/* 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 Lpc{
+  // en/decode lookups
+  Drft fft=new Drft();;
+
+  int ln;
+  int m;
+
+  // Autocorrelation LPC coeff generation algorithm invented by
+  // N. Levinson in 1947, modified by J. Durbin in 1959.
+
+  // Input : n elements of time doamin data
+  // Output: m lpc coefficients, excitation energy
+
+  static float lpc_from_data(float[] data, float[] lpc,int n,int m){
+    float[] aut=new float[m+1];
+    float error;
+    int i,j;
+
+    // autocorrelation, p+1 lag coefficients
+
+    j=m+1;
+    while(j--!=0){
+      float d=0;
+      for(i=j;i<n;i++)d+=data[i]*data[i-j];
+      aut[j]=d;
+    }
+  
+    // Generate lpc coefficients from autocorr values
+
+    error=aut[0];
+    /*
+    if(error==0){
+      for(int k=0; k<m; k++) lpc[k]=0.0f;
+      return 0;
+    }
+    */
+  
+    for(i=0;i<m;i++){
+      float r=-aut[i+1];
+
+      if(error==0){
+        for(int k=0; k<m; k++) lpc[k]=0.0f;
+        return 0;
+      }
+
+      // Sum up this iteration's reflection coefficient; note that in
+      // Vorbis we don't save it.  If anyone wants to recycle this code
+      // and needs reflection coefficients, save the results of 'r' from
+      // each iteration.
+
+      for(j=0;j<i;j++)r-=lpc[j]*aut[i-j];
+      r/=error; 
+
+      // Update LPC coefficients and total error
+    
+      lpc[i]=r;
+      for(j=0;j<i/2;j++){
+       float tmp=lpc[j];
+       lpc[j]+=r*lpc[i-1-j];
+       lpc[i-1-j]+=r*tmp;
+      }
+      if(i%2!=0)lpc[j]+=lpc[j]*r;
+    
+      error*=1.0-r*r;
+    }
+  
+    // we need the error value to know how big an impulse to hit the
+    // filter with later
+  
+    return error;
+  }
+
+  // Input : n element envelope spectral curve
+  // Output: m lpc coefficients, excitation energy
+
+  float lpc_from_curve(float[] curve, float[] lpc){
+    int n=ln;
+    float[] work=new float[n+n];
+    float fscale=(float)(.5/n);
+    int i,j;
+  
+    // input is a real curve. make it complex-real
+    // This mixes phase, but the LPC generation doesn't care.
+    for(i=0;i<n;i++){
+      work[i*2]=curve[i]*fscale;
+      work[i*2+1]=0;
+    }
+    work[n*2-1]=curve[n-1]*fscale;
+  
+    n*=2;
+    fft.backward(work);
+  
+    // The autocorrelation will not be circular.  Shift, else we lose
+    // most of the power in the edges.
+  
+    for(i=0,j=n/2;i<n/2;){
+      float temp=work[i];
+      work[i++]=work[j];
+      work[j++]=temp;
+    }
+  
+    return(lpc_from_data(work,lpc,n,m));
+  }
+
+  void init(int mapped, int m){
+    //memset(l,0,sizeof(lpc_lookup));
+
+    ln=mapped;
+    this.m=m;
+
+    // we cheat decoding the LPC spectrum via FFTs
+    fft.init(mapped*2);
+  }
+
+  void clear(){
+    fft.clear();
+  }
+
+  static float FAST_HYPOT(float a, float b){
+    return (float)Math.sqrt((a)*(a) + (b)*(b));
+  }
+
+  // One can do this the long way by generating the transfer function in
+  // the time domain and taking the forward FFT of the result.  The
+  // results from direct calculation are cleaner and faster. 
+  //
+  // This version does a linear curve generation and then later
+  // interpolates the log curve from the linear curve.
+
+  void lpc_to_curve(float[] curve, float[] lpc, float amp){
+
+    //memset(curve,0,sizeof(float)*l->ln*2);
+    for(int i=0; i<ln*2; i++)curve[i]=0.0f;
+
+    if(amp==0)return;
+
+    for(int i=0;i<m;i++){
+      curve[i*2+1]=lpc[i]/(4*amp);
+      curve[i*2+2]=-lpc[i]/(4*amp);
+    }
+
+    fft.backward(curve); // reappropriated ;-)
+
+    {
+      int l2=ln*2;
+      float unit=(float)(1./amp);
+      curve[0]=(float)(1./(curve[0]*2+unit));
+      for(int i=1;i<ln;i++){
+       float real=(curve[i]+curve[l2-i]);
+       float imag=(curve[i]-curve[l2-i]);
+
+       float a = real + unit;
+       curve[i] = (float)(1.0 / FAST_HYPOT(a, imag));
+      }
+    }
+  }  
+
+/*
+  // subtract or add an lpc filter to data.  Vorbis doesn't actually use this.
+
+  static void lpc_residue(float[] coeff, float[] prime,int m,
+                         float[] data, int n){
+
+    // in: coeff[0...m-1] LPC coefficients 
+    //     prime[0...m-1] initial values 
+    //     data[0...n-1] data samples 
+    // out: data[0...n-1] residuals from LPC prediction
+
+    float[] work=new float[m+n];
+    float y;
+
+    if(prime==null){
+      for(int i=0;i<m;i++){
+       work[i]=0;
+      }
+    }
+    else{
+      for(int i=0;i<m;i++){
+       work[i]=prime[i];
+      }
+    }
+
+    for(int i=0;i<n;i++){
+      y=0;
+      for(int j=0;j<m;j++){
+       y-=work[i+j]*coeff[m-j-1];
+      }
+      work[i+m]=data[i];
+      data[i]-=y;
+    }
+  }
+
+  static void lpc_predict(float[] coeff, float[] prime,int m,
+                         float[] data, int n){
+
+    // in: coeff[0...m-1] LPC coefficients 
+    //     prime[0...m-1] initial values (allocated size of n+m-1)
+    //     data[0...n-1] residuals from LPC prediction   
+    // out: data[0...n-1] data samples
+
+    int o,p;
+    float y;
+    float[] work=new float[m+n];
+
+    if(prime==null){
+      for(int i=0;i<m;i++){
+       work[i]=0.f;
+      }
+    }
+    else{
+      for(int i=0;i<m;i++){
+       work[i]=prime[i];
+      }
+    }
+
+    for(int i=0;i<n;i++){
+      y=data[i];
+      o=i;
+      p=m;
+      for(int j=0;j<m;j++){
+       y-=work[o++]*coeff[--p];
+      }
+      data[i]=work[o]=y;
+    }
+  }
+*/
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Lsp.java b/jorbis/src/com/jcraft/jorbis/Lsp.java
new file mode 100644 (file)
index 0000000..550c7d6
--- /dev/null
@@ -0,0 +1,111 @@
+/* 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;
+
+/*
+  function: LSP (also called LSF) conversion routines
+
+  The LSP generation code is taken (with minimal modification) from
+  "On the Computation of the LSP Frequencies" by Joseph Rothweiler
+  <rothwlr@altavista.net>, available at:
+  
+  http://www2.xtdl.com/~rothwlr/lsfpaper/lsfpage.html 
+ ********************************************************************/
+
+class Lsp{
+
+  static final float M_PI=(float)(3.1415926539);
+
+  static void lsp_to_curve(float[] curve,
+                          int[] map, int n, int ln,
+                          float[] lsp, int m,
+                          float amp, float ampoffset){
+    int i;
+    float wdel=M_PI/ln;
+    for(i=0;i<m;i++)lsp[i]=Lookup.coslook(lsp[i]);
+    int m2=(m/2)*2;
+
+    i=0;
+    while(i<n){
+      int k=map[i];
+      float p=.7071067812f;
+      float q=.7071067812f;
+      float w=Lookup.coslook(wdel*k);
+      int ftmp=0;
+      int c=m>>>1;
+
+      for(int j=0;j<m2;j+=2){
+        q*=lsp[j]-w;
+       p*=lsp[j+1]-w;
+      }
+
+      if((m&1)!=0){
+        /* odd order filter; slightly assymetric */
+        /* the last coefficient */
+        q*=lsp[m-1]-w;
+        q*=q;
+        p*=p*(1.f-w*w);
+      }
+      else{
+        /* even order filter; still symmetric */
+        q*=q*(1.f+w);
+        p*=p*(1.f-w);
+      }
+
+      //  q=frexp(p+q,&qexp);
+      q=p+q;
+      int hx=Float.floatToIntBits(q);
+      int ix=0x7fffffff&hx;
+      int qexp=0;
+
+      if(ix>=0x7f800000||(ix==0)){
+        // 0,inf,nan
+      }
+      else{
+        if(ix<0x00800000){            // subnormal
+         q*=3.3554432000e+07;        // 0x4c000000
+          hx=Float.floatToIntBits(q);
+          ix=0x7fffffff&hx;
+         qexp=-25;
+        }
+        qexp += ((ix>>>23)-126);
+        hx=(hx&0x807fffff)|0x3f000000;
+        q=Float.intBitsToFloat(hx);
+      }
+
+      q=Lookup.fromdBlook(amp*
+                         Lookup.invsqlook(q)*
+                         Lookup.invsq2explook(qexp+m)-ampoffset);
+
+      do{curve[i++]*=q;}
+//    do{curve[i++]=q;}
+      while(i<n&&map[i]==k);
+
+    }
+  }
+}
+
+
diff --git a/jorbis/src/com/jcraft/jorbis/Mapping0.java b/jorbis/src/com/jcraft/jorbis/Mapping0.java
new file mode 100644 (file)
index 0000000..a2c3d06
--- /dev/null
@@ -0,0 +1,566 @@
+/* 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 Mapping0 extends FuncMapping{
+  static int seq=0;
+  void free_info(Object imap){};
+  void free_look(Object imap){
+/*
+    LookMapping0 l=(LookMapping0)imap;
+    InfoMapping0 info=l.map;
+    if(l!=null){
+      for(int i=0;i<l.map.submaps;i++){
+        l.time_func[i].free_look(l.time_look[i]);
+        l.floor_func[i].free_look(l.floor_look[i]);
+        l.residue_func[i].free_look(l.residue_look[i]);
+        if(l.psy_look!=null)l.psy_look[i].clear();
+      }
+    }
+
+    if(l.floor_state!=null){
+      for(int i=0;i<l.ch;i++)
+       l.floor_func[info.chmuxlist[i]].free_state(l.floor_state[i]);
+      //free(l.floor_state);
+    }
+
+    if(l.decay!=null){
+      for(int i=0;i<l.ch;i++){
+       //if(l.decay[i])free(l->decay[i]);
+        l.decay[i]=null;
+      }
+      //free(l->decay);
+      l.decay=null;
+    }
+    //free(l->time_func);
+    //free(l->floor_func);
+    //free(l->residue_func);
+    //free(l->time_look);
+    //free(l->floor_look);
+    //free(l->residue_look);
+    //f(l->psy_look)free(l->psy_look);
+    l.time_func=null;
+    l.floor_func=null;
+    l.residue_func=null;
+    l.time_look=null;
+    l.floor_look=null;
+    l.residue_look=null;
+    //memset(l,0,sizeof(vorbis_look_mapping0));
+    //free(l);
+*/
+  }
+
+  Object look(DspState vd, InfoMode vm, Object m){
+//System.err.println("Mapping0.look");
+    Info vi=vd.vi;
+    LookMapping0 look=new LookMapping0();
+    InfoMapping0 info=look.map=(InfoMapping0)m;
+    look.mode=vm;
+  
+    look.time_look=new Object[info.submaps];
+    look.floor_look=new Object[info.submaps];
+    look.residue_look=new Object[info.submaps];
+
+/*
+    if(vd.analysisp!=0){
+      look.floor_state=new Object[vi.channels];
+    }
+    if(vi.psys!=0){
+      look.psy_look=new PsyLook[info.submaps];
+      for(int i=0; i<info.submaps; i++){ look.psy_look[i]=new PsyLook(); }
+    }
+*/
+
+    look.time_func=new FuncTime[info.submaps];
+    look.floor_func=new FuncFloor[info.submaps];
+    look.residue_func=new FuncResidue[info.submaps];
+  
+    for(int i=0;i<info.submaps;i++){
+      int timenum=info.timesubmap[i];
+      int floornum=info.floorsubmap[i];
+      int resnum=info.residuesubmap[i];
+
+      look.time_func[i]=FuncTime.time_P[vi.time_type[timenum]];
+      look.time_look[i]=look.time_func[i].look(vd,vm,vi.time_param[timenum]);
+      look.floor_func[i]=FuncFloor.floor_P[vi.floor_type[floornum]];
+      look.floor_look[i]=look.floor_func[i].
+                         look(vd,vm,vi.floor_param[floornum]);
+      look.residue_func[i]=FuncResidue.residue_P[vi.residue_type[resnum]];
+      look.residue_look[i]=look.residue_func[i].
+                           look(vd,vm,vi.residue_param[resnum]);
+
+/*    
+      if(vi.psys!=0 && vd.analysisp!=0){
+        int psynum=info.psysubmap[i];
+       look.psy_look[i].init(vi.psy_param[psynum],
+                             vi.blocksizes[vm.blockflag]/2,vi.rate);
+      }
+*/
+    }
+
+    if(vi.psys!=0 && vd.analysisp!=0){
+       /*
+    if(info->psy[0] != info->psy[1]){
+
+      int psynum=info->psy[0];
+      look->psy_look[0]=_ogg_calloc(1,sizeof(vorbis_look_psy));      
+      _vp_psy_init(look->psy_look[0],ci->psy_param[psynum],
+                  ci->psy_g_param,
+                  ci->blocksizes[vm->blockflag]/2,vi->rate);
+
+      psynum=info->psy[1];
+      look->psy_look[1]=_ogg_calloc(1,sizeof(vorbis_look_psy));      
+      _vp_psy_init(look->psy_look[1],ci->psy_param[psynum],
+                  ci->psy_g_param,
+                  ci->blocksizes[vm->blockflag]/2,vi->rate);
+    }else{
+
+      int psynum=info->psy[0];
+      look->psy_look[0]=_ogg_calloc(1,sizeof(vorbis_look_psy));      
+      look->psy_look[1]=look->psy_look[0];
+     _vp_psy_init(look->psy_look[0],ci->psy_param[psynum],
+                  ci->psy_g_param,
+                  ci->blocksizes[vm->blockflag]/2,vi->rate);
+
+    }
+       */
+    }
+
+    look.ch=vi.channels;
+//  if(vd->analysisp)drft_init(&look->fft_look,ci->blocksizes[vm->blockflag]);
+
+    return(look);
+//return null;
+  }
+
+  void pack(Info vi, Object imap, Buffer opb){
+    InfoMapping0 info=(InfoMapping0)imap;
+
+  /* another 'we meant to do it this way' hack...  up to beta 4, we
+     packed 4 binary zeros here to signify one submapping in use.  We
+     now redefine that to mean four bitflags that indicate use of
+     deeper features; bit0:submappings, bit1:coupling,
+     bit2,3:reserved. This is backward compatable with all actual uses
+     of the beta code. */
+
+    if(info.submaps>1){
+      opb.write(1,1);
+      opb.write(info.submaps-1,4);
+    }
+    else{
+      opb.write(0,1);
+    }
+
+    if(info.coupling_steps>0){
+      opb.write(1,1);
+      opb.write(info.coupling_steps-1,8);
+      for(int i=0;i<info.coupling_steps;i++){
+        opb.write(info.coupling_mag[i],ilog2(vi.channels));
+        opb.write(info.coupling_ang[i],ilog2(vi.channels));
+      }
+    }
+    else{
+      opb.write(0,1);
+    }
+  
+    opb.write(0,2); /* 2,3:reserved */
+
+    /* we don't write the channel submappings if we only have one... */
+    if(info.submaps>1){
+      for(int i=0;i<vi.channels;i++)
+        opb.write(info.chmuxlist[i],4);
+    }
+    for(int i=0;i<info.submaps;i++){
+      opb.write(info.timesubmap[i],8);
+      opb.write(info.floorsubmap[i],8);
+      opb.write(info.residuesubmap[i],8);
+    }
+  }
+
+  // also responsible for range checking
+  Object unpack(Info vi, Buffer opb){
+    InfoMapping0 info=new InfoMapping0();
+
+    // !!!!
+    if(opb.read(1)!=0){
+      info.submaps=opb.read(4)+1;
+    }
+    else{
+      info.submaps=1;
+    }
+
+    if(opb.read(1)!=0){
+      info.coupling_steps=opb.read(8)+1;
+
+      for(int i=0;i<info.coupling_steps;i++){
+        int testM=info.coupling_mag[i]=opb.read(ilog2(vi.channels));
+        int testA=info.coupling_ang[i]=opb.read(ilog2(vi.channels));
+
+        if(testM<0 ||
+           testA<0 ||
+           testM==testA ||
+           testM>=vi.channels ||
+           testA>=vi.channels){
+         //goto err_out;
+          info.free();
+          return(null);
+        }
+      }
+    }
+
+    if(opb.read(2)>0){ /* 2,3:reserved */
+      //goto err_out;
+      info.free();
+      return(null);
+    }
+
+    if(info.submaps>1){
+      for(int i=0;i<vi.channels;i++){
+        info.chmuxlist[i]=opb.read(4);
+        if(info.chmuxlist[i]>=info.submaps){
+         //goto err_out;
+          info.free();
+          return(null);
+       }
+      }
+    }
+
+    for(int i=0;i<info.submaps;i++){
+      info.timesubmap[i]=opb.read(8);
+      if(info.timesubmap[i]>=vi.times){
+        //goto err_out;
+        info.free();
+        return(null);
+      }
+      info.floorsubmap[i]=opb.read(8);
+      if(info.floorsubmap[i]>=vi.floors){
+       //goto err_out;
+        info.free();
+        return(null);
+      }
+      info.residuesubmap[i]=opb.read(8);
+      if(info.residuesubmap[i]>=vi.residues){
+       //goto err_out;
+        info.free();
+        return(null);
+      }
+    }
+    return info;
+    //err_out:
+    //free_info(info);
+    //return(NULL);
+  }
+
+/*
+  // no time mapping implementation for now 
+  static int seq=0;
+  int forward(Block vb, Object l){
+    DspState vd=vb.vd;
+    Info vi=vd.vi;
+    LookMapping0 look=(LookMapping0)l;
+    InfoMapping0 info=look.map;
+    InfoMode mode=look.mode;
+    int n=vb.pcmend;
+    float[] window=vd.window[vb.W][vb.lW][vb.nW][mode.windowtype];
+
+    float[][] pcmbundle=new float[vi.channles][];
+    int[] nonzero=new int[vi.channels];
+     // time domain pre-window: NONE IMPLEMENTED
+
+     // window the PCM data: takes PCM vector, vb; modifies PCM vector
+
+     for(int i=0;i<vi.channels;i++){
+       float[] pcm=vb.pcm[i];
+       for(int j=0;j<n;j++)
+         pcm[j]*=window[j];
+     }
+           
+     // time-domain post-window: NONE IMPLEMENTED
+
+     // transform the PCM data; takes PCM vector, vb; modifies PCM vector
+     // only MDCT right now....
+     for(int i=0;i<vi.channels;i++){
+       float[] pcm=vb.pcm[i];
+       mdct_forward(vd.transform[vb.W][0],pcm,pcm);
+     }
+
+     {
+       float[] floor=_vorbis_block_alloc(vb,n*sizeof(float)/2);
+    
+       for(int i=0;i<vi.channels;i++){
+         float[] pcm=vb.pcm[i];
+         float[] decay=look.decay[i];
+         int submap=info.chmuxlist[i];
+
+         // if some other mode/mapping was called last frame, our decay
+         // accumulator is out of date.  Clear it.
+         //if(look.lastframe+1 != vb->sequence)
+        //  memset(decay,0,n*sizeof(float)/2);
+
+         // perform psychoacoustics; do masking
+         _vp_compute_mask(look.psy_look[submap],pcm,floor,decay);
+         _analysis_output("mdct",seq,pcm,n/2,0,1);
+         _analysis_output("lmdct",seq,pcm,n/2,0,0);
+         _analysis_output("prefloor",seq,floor,n/2,0,1);
+
+         // perform floor encoding
+         nonzero[i]=look.floor_func[submap].
+                   forward(vb,look.floor_look[submap],floor,floor,look.floor_state[i]);
+
+         _analysis_output("floor",seq,floor,n/2,0,1);
+
+         // apply the floor, do optional noise levelling
+         _vp_apply_floor(look->psy_look+submap,pcm,floor);
+      
+         _analysis_output("res",seq++,pcm,n/2,0,0);
+     }
+    
+     // perform residue encoding with residue mapping; this is
+     // multiplexed.  All the channels belonging to one submap are
+     // encoded (values interleaved), then the next submap, etc
+    
+     for(int i=0;i<info.submaps;i++){
+       int ch_in_bundle=0;
+        for(int j=0;j<vi.channels;j++){
+         if(info.chmuxlist[j]==i && nonzero[j]==1){
+           pcmbundle[ch_in_bundle++]=vb.pcm[j];
+         }
+        }
+        look.residue_func[i].forward(vb,look.residue_look[i], pcmbundle,ch_in_bundle);
+      }
+    }
+    look.lastframe=vb.sequence;
+    return(0);
+  }
+*/
+
+  float[][] pcmbundle=null;
+  int[] zerobundle=null;
+  int[] nonzero=null;
+  Object[] floormemo=null;
+
+  synchronized int inverse(Block vb, Object l){
+    //System.err.println("Mapping0.inverse");
+    DspState vd=vb.vd;
+    Info vi=vd.vi;
+    LookMapping0 look=(LookMapping0)l;
+    InfoMapping0 info=look.map;
+    InfoMode mode=look.mode;
+    int n=vb.pcmend=vi.blocksizes[vb.W];
+
+    float[] window=vd.window[vb.W][vb.lW][vb.nW][mode.windowtype];
+    // float[][] pcmbundle=new float[vi.channels][];
+    // int[] nonzero=new int[vi.channels];
+    if(pcmbundle==null || pcmbundle.length<vi.channels){
+      pcmbundle=new float[vi.channels][];
+      nonzero=new int[vi.channels];
+      zerobundle=new int[vi.channels];
+      floormemo=new Object[vi.channels];
+    }
+  
+    // time domain information decode (note that applying the
+    // information would have to happen later; we'll probably add a
+    // function entry to the harness for that later
+    // NOT IMPLEMENTED
+
+    // recover the spectral envelope; store it in the PCM vector for now 
+    for(int i=0;i<vi.channels;i++){
+      float[] pcm=vb.pcm[i];
+      int submap=info.chmuxlist[i];
+
+      floormemo[i]=look.floor_func[submap].inverse1(vb,look.
+                                                   floor_look[submap],
+                                                   floormemo[i]
+                                                   );
+      if(floormemo[i]!=null){ nonzero[i]=1; }
+      else{ nonzero[i]=0; }
+      for(int j=0; j<n/2; j++){
+        pcm[j]=0;
+      }                 
+
+      //_analysis_output("ifloor",seq+i,pcm,n/2,0,1);
+    }
+
+    for(int i=0; i<info.coupling_steps; i++){
+      if(nonzero[info.coupling_mag[i]]!=0 ||
+         nonzero[info.coupling_ang[i]]!=0){
+        nonzero[info.coupling_mag[i]]=1;
+        nonzero[info.coupling_ang[i]]=1;
+      }
+    }
+
+    // recover the residue, apply directly to the spectral envelope
+
+    for(int i=0;i<info.submaps;i++){
+      int ch_in_bundle=0;
+      for(int j=0;j<vi.channels;j++){
+        if(info.chmuxlist[j]==i){
+          if(nonzero[j]!=0){
+            zerobundle[ch_in_bundle]=1;
+         }
+          else{
+            zerobundle[ch_in_bundle]=0;
+         }
+         pcmbundle[ch_in_bundle++]=vb.pcm[j];
+       }
+      }
+
+      look.residue_func[i].inverse(vb,look.residue_look[i],
+                                  pcmbundle,zerobundle,ch_in_bundle);
+    }
+
+
+    for(int i=info.coupling_steps-1;i>=0;i--){
+      float[] pcmM=vb.pcm[info.coupling_mag[i]];
+      float[] pcmA=vb.pcm[info.coupling_ang[i]];
+
+      for(int j=0;j<n/2;j++){
+        float mag=pcmM[j];
+        float ang=pcmA[j];
+
+        if(mag>0){
+          if(ang>0){
+            pcmM[j]=mag;
+            pcmA[j]=mag-ang;
+          }
+          else{
+            pcmA[j]=mag;
+            pcmM[j]=mag+ang;
+          }
+       }
+        else{
+          if(ang>0){
+            pcmM[j]=mag;
+            pcmA[j]=mag+ang;
+          }
+          else{
+            pcmA[j]=mag;
+            pcmM[j]=mag-ang;
+          }
+       }
+      }
+    }
+
+//    /* compute and apply spectral envelope */
+
+    for(int i=0;i<vi.channels;i++){
+      float[] pcm=vb.pcm[i];
+      int submap=info.chmuxlist[i];
+      look.floor_func[submap].inverse2(vb,look.floor_look[submap],floormemo[i],pcm);
+    }
+
+    // transform the PCM data; takes PCM vector, vb; modifies PCM vector
+    // only MDCT right now....
+
+    for(int i=0;i<vi.channels;i++){
+      float[] pcm=vb.pcm[i];
+      //_analysis_output("out",seq+i,pcm,n/2,0,0);
+      ((Mdct)vd.transform[vb.W][0]).backward(pcm,pcm);
+    }
+
+    // now apply the decoded pre-window time information
+    // NOT IMPLEMENTED
+  
+    // window the data
+    for(int i=0;i<vi.channels;i++){
+      float[] pcm=vb.pcm[i];
+      if(nonzero[i]!=0){
+        for(int j=0;j<n;j++){
+         pcm[j]*=window[j];
+        }
+      }
+      else{
+        for(int j=0;j<n;j++){
+         pcm[j]=0.f;
+       }
+      }
+      //_analysis_output("final",seq++,pcm,n,0,0);
+    }
+           
+    // now apply the decoded post-window time information
+    // NOT IMPLEMENTED
+    // all done!
+    return(0);
+  }
+
+
+  private static int ilog2(int v){
+    int ret=0;
+    while(v>1){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+}
+
+class InfoMapping0{
+  int   submaps;  // <= 16
+  int[] chmuxlist=new int[256];   // up to 256 channels in a Vorbis stream
+  
+  int[] timesubmap=new int[16];   // [mux]
+  int[] floorsubmap=new int[16];  // [mux] submap to floors
+  int[] residuesubmap=new int[16];// [mux] submap to residue
+  int[] psysubmap=new int[16];    // [mux]; encode only
+
+  int   coupling_steps;
+  int[] coupling_mag=new int[256];
+  int[] coupling_ang=new int[256];
+
+  void free(){
+    chmuxlist=null;
+    timesubmap=null;
+    floorsubmap=null;
+    residuesubmap=null;
+    psysubmap=null;
+
+    coupling_mag=null;
+    coupling_ang=null;
+  }
+}
+
+class LookMapping0{
+  InfoMode mode;
+  InfoMapping0 map;
+  Object[] time_look;
+  Object[] floor_look;
+  Object[] floor_state;
+  Object[] residue_look; 
+  PsyLook[] psy_look;
+
+  FuncTime[] time_func; 
+  FuncFloor[] floor_func; 
+  FuncResidue[] residue_func;
+
+  int ch;
+  float[][] decay;
+  int lastframe; // if a different mode is called, we need to 
+                 // invalidate decay and floor state
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Mdct.java b/jorbis/src/com/jcraft/jorbis/Mdct.java
new file mode 100644 (file)
index 0000000..bd5cc38
--- /dev/null
@@ -0,0 +1,249 @@
+/* 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 Mdct{
+
+  static private final float cPI3_8=0.38268343236508977175f;
+  static private final float cPI2_8=0.70710678118654752441f;
+  static private final float cPI1_8=0.92387953251128675613f;
+
+  int n;
+  int log2n;
+  
+  float[] trig;
+  int[] bitrev;
+
+  float scale;
+
+  void init(int n){
+    bitrev=new int[n/4];
+    trig=new float[n+n/4];
+
+    int n2=n>>>1;
+    log2n=(int)Math.rint(Math.log(n)/Math.log(2));
+    this.n=n;
+
+
+    int AE=0;
+    int AO=1;
+    int BE=AE+n/2;
+    int BO=BE+1;
+    int CE=BE+n/2;
+    int CO=CE+1;
+    // trig lookups...
+    for(int i=0;i<n/4;i++){
+      trig[AE+i*2]=(float)Math.cos((Math.PI/n)*(4*i));
+      trig[AO+i*2]=(float)-Math.sin((Math.PI/n)*(4*i));
+      trig[BE+i*2]=(float)Math.cos((Math.PI/(2*n))*(2*i+1));
+      trig[BO+i*2]=(float)Math.sin((Math.PI/(2*n))*(2*i+1));
+    }
+    for(int i=0;i<n/8;i++){
+      trig[CE+i*2]=(float)Math.cos((Math.PI/n)*(4*i+2));
+      trig[CO+i*2]=(float)-Math.sin((Math.PI/n)*(4*i+2));
+    }
+
+    {
+      int mask=(1<<(log2n-1))-1;
+      int msb=1<<(log2n-2);
+      for(int i=0;i<n/8;i++){
+       int acc=0;
+       for(int j=0;msb>>>j!=0;j++)
+         if(((msb>>>j)&i)!=0)acc|=1<<j;
+       bitrev[i*2]=((~acc)&mask);
+//     bitrev[i*2]=((~acc)&mask)-1;
+       bitrev[i*2+1]=acc;
+      }
+    }
+    scale=4.f/n;
+  }
+
+  void clear(){
+  }
+
+  void forward(float[] in, float[] out){
+  }
+
+  float[] _x=new float[1024];
+  float[] _w=new float[1024];
+
+  synchronized void backward(float[] in, float[] out){
+    if(_x.length<n/2){_x=new float[n/2];}
+    if(_w.length<n/2){_w=new float[n/2];}
+    float[] x=_x;
+    float[] w=_w;
+    int n2=n>>>1;
+    int n4=n>>>2;
+    int n8=n>>>3;
+
+    // rotate + step 1
+    {
+      int inO=1;
+      int xO=0;
+      int A=n2;
+
+      int i;
+      for(i=0;i<n8;i++){
+       A-=2;
+       x[xO++]=-in[inO+2]*trig[A+1] - in[inO]*trig[A];
+       x[xO++]= in[inO]*trig[A+1] - in[inO+2]*trig[A];
+       inO+=4;
+      }
+
+      inO=n2-4;
+
+      for(i=0;i<n8;i++){
+       A-=2;
+       x[xO++]=in[inO]*trig[A+1] + in[inO+2]*trig[A];
+       x[xO++]=in[inO]*trig[A] - in[inO+2]*trig[A+1];
+       inO-=4;
+      }
+    }
+
+    float[] xxx=mdct_kernel(x,w,n,n2,n4,n8);
+    int xx=0;
+
+    // step 8
+
+    {
+      int B=n2;
+      int o1=n4,o2=o1-1;
+      int o3=n4+n2,o4=o3-1;
+    
+      for(int i=0;i<n4;i++){
+       float temp1= (xxx[xx] * trig[B+1] - xxx[xx+1] * trig[B]);
+       float temp2=-(xxx[xx] * trig[B] + xxx[xx+1] * trig[B+1]);
+    
+       out[o1]=-temp1;
+       out[o2]= temp1;
+       out[o3]= temp2;
+       out[o4]= temp2;
+
+       o1++;
+       o2--;
+       o3++;
+       o4--;
+       xx+=2;
+       B+=2;
+      }
+    }
+  }
+  private float[] mdct_kernel(float[] x, float[] w,
+                              int n, int n2, int n4, int n8){
+    // step 2
+
+    int xA=n4;
+    int xB=0;
+    int w2=n4;
+    int A=n2;
+
+    for(int i=0;i<n4;){
+      float x0=x[xA] - x[xB];
+      float x1;
+      w[w2+i]=x[xA++]+x[xB++];
+
+      x1=x[xA]-x[xB];
+      A-=4;
+
+      w[i++]=   x0 * trig[A] + x1 * trig[A+1];
+      w[i]=     x1 * trig[A] - x0 * trig[A+1];
+
+      w[w2+i]=x[xA++]+x[xB++];
+      i++;
+    }
+
+    // step 3
+
+    {
+      for(int i=0;i<log2n-3;i++){
+        int k0=n>>>(i+2);
+       int k1=1<<(i+3);
+       int wbase=n2-2;
+
+       A=0;
+       float[] temp;
+
+       for(int r=0;r<(k0>>>2);r++){
+         int w1=wbase;
+         w2=w1-(k0>>1);
+         float AEv= trig[A],wA;
+         float AOv= trig[A+1],wB;
+         wbase-=2;
+                     
+         k0++;
+         for(int s=0;s<(2<<i);s++){
+           wB     =w[w1]   -w[w2];
+           x[w1]  =w[w1]   +w[w2];
+
+           wA     =w[++w1] -w[++w2];
+           x[w1]  =w[w1]   +w[w2];
+           
+           x[w2]  =wA*AEv  - wB*AOv;
+           x[w2-1]=wB*AEv  + wA*AOv;
+
+           w1-=k0;
+           w2-=k0;
+         }
+         k0--;
+         A+=k1;
+       }
+
+       temp=w;
+       w=x;
+       x=temp;
+      }
+    }
+
+    // step 4, 5, 6, 7
+    {
+      int C=n;
+      int bit=0;
+      int x1=0;
+      int x2=n2-1;
+
+      for(int i=0;i<n8;i++){
+       int t1=bitrev[bit++];
+       int t2=bitrev[bit++];
+
+       float wA=w[t1]-w[t2+1];
+       float wB=w[t1-1]+w[t2];
+       float wC=w[t1]+w[t2+1];
+       float wD=w[t1-1]-w[t2];
+
+       float wACE=wA* trig[C];
+       float wBCE=wB* trig[C++];
+       float wACO=wA* trig[C];
+       float wBCO=wB* trig[C++];
+      
+       x[x1++]=( wC+wACO+wBCE)*.5f;
+       x[x2--]=(-wD+wBCO-wACE)*.5f;
+       x[x1++]=( wD+wBCO-wACE)*.5f; 
+       x[x2--]=( wC-wACO-wBCE)*.5f;
+      }
+    }
+    return(x);
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/PsyInfo.java b/jorbis/src/com/jcraft/jorbis/PsyInfo.java
new file mode 100644 (file)
index 0000000..599c41e
--- /dev/null
@@ -0,0 +1,72 @@
+/* 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;
+
+// psychoacoustic setup
+class PsyInfo{ 
+  int    athp;
+  int    decayp;
+  int    smoothp;
+  int    noisefitp;
+  int    noisefit_subblock;
+  float noisefit_threshdB;
+
+  float ath_att;
+
+  int tonemaskp;
+  float[] toneatt_125Hz=new float[5];
+  float[] toneatt_250Hz=new float[5];
+  float[] toneatt_500Hz=new float[5];
+  float[] toneatt_1000Hz=new float[5];
+  float[] toneatt_2000Hz=new float[5];
+  float[] toneatt_4000Hz=new float[5];
+  float[] toneatt_8000Hz=new float[5];
+
+  int peakattp;
+  float[] peakatt_125Hz=new float[5];
+  float[] peakatt_250Hz=new float[5];
+  float[] peakatt_500Hz=new float[5];
+  float[] peakatt_1000Hz=new float[5];
+  float[] peakatt_2000Hz=new float[5];
+  float[] peakatt_4000Hz=new float[5];
+  float[] peakatt_8000Hz=new float[5];
+
+  int noisemaskp;
+  float[] noiseatt_125Hz=new float[5];
+  float[] noiseatt_250Hz=new float[5];
+  float[] noiseatt_500Hz=new float[5];
+  float[] noiseatt_1000Hz=new float[5];
+  float[] noiseatt_2000Hz=new float[5];
+  float[] noiseatt_4000Hz=new float[5];
+  float[] noiseatt_8000Hz=new float[5];
+
+  float max_curve_dB;
+
+  float attack_coeff;
+  float decay_coeff;
+
+  void free(){}
+}
diff --git a/jorbis/src/com/jcraft/jorbis/PsyLook.java b/jorbis/src/com/jcraft/jorbis/PsyLook.java
new file mode 100644 (file)
index 0000000..9da85ed
--- /dev/null
@@ -0,0 +1,187 @@
+/* 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 PsyLook {
+  int n;
+  PsyInfo vi;
+
+  float[][][] tonecurves;
+  float[][] peakatt;
+  float[][][] noisecurves;
+
+  float[] ath;
+  int[] octave;
+
+  void init(PsyInfo vi, int n, int rate){
+    /*
+    float rate2=rate/2.;
+    //memset(p,0,sizeof(vorbis_look_psy));
+    ath=new float[n];
+    octave=new int[n];
+    this.vi=vi;
+    this.n=n;
+
+    // set up the lookups for a given blocksize and sample rate
+    // Vorbis max sample rate is limited by 26 Bark (54kHz)
+    set_curve(ATH_Bark_dB, ath,n,rate);
+    for(int i=0;i<n;i++)
+      ath[i]=fromdB(ath[i]+vi.ath_att);
+
+    for(int i=0;i<n;i++){
+      int oc=rint(toOC((i+.5)*rate2/n)*2.);
+      if(oc<0)oc=0;
+      if(oc>12)oc=12;
+      octave[i]=oc;
+    }  
+
+    tonecurves=malloc(13*sizeof(float **));
+    noisecurves=malloc(13*sizeof(float **));
+    peakatt=malloc(7*sizeof(float *));
+    for(int i=0;i<13;i++){
+      tonecurves[i]=malloc(9*sizeof(float *));
+      noisecurves[i]=malloc(9*sizeof(float *));
+    }
+    for(i=0;i<7;i++)
+      peakatt[i]=malloc(5*sizeof(float));
+
+    for(i=0;i<13;i++){
+      for(j=0;j<9;j++){
+       tonecurves[i][j]=malloc(EHMER_MAX*sizeof(float));
+       noisecurves[i][j]=malloc(EHMER_MAX*sizeof(float));
+      }
+    }
+
+    // OK, yeah, this was a silly way to do it
+    memcpy(tonecurves[0][2],tone_125_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[0][4],tone_125_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[0][6],tone_125_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[0][8],tone_125_100dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(tonecurves[2][2],tone_250_40dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[2][4],tone_250_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[2][6],tone_250_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[2][8],tone_250_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(tonecurves[4][2],tone_500_40dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[4][4],tone_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[4][6],tone_500_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[4][8],tone_500_100dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(tonecurves[6][2],tone_1000_40dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[6][4],tone_1000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[6][6],tone_1000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[6][8],tone_1000_100dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(tonecurves[8][2],tone_2000_40dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[8][4],tone_2000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[8][6],tone_2000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[8][8],tone_2000_100dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(tonecurves[10][2],tone_4000_40dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[10][4],tone_4000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[10][6],tone_4000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[10][8],tone_4000_100dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(tonecurves[12][2],tone_4000_40dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[12][4],tone_4000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[12][6],tone_8000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(tonecurves[12][8],tone_8000_100dB_SL,sizeof(float)*EHMER_MAX);
+
+
+    memcpy(noisecurves[0][2],noise_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[0][4],noise_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[0][6],noise_500_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[0][8],noise_500_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(noisecurves[2][2],noise_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[2][4],noise_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[2][6],noise_500_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[2][8],noise_500_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(noisecurves[4][2],noise_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[4][4],noise_500_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[4][6],noise_500_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[4][8],noise_500_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(noisecurves[6][2],noise_1000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[6][4],noise_1000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[6][6],noise_1000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[6][8],noise_1000_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(noisecurves[8][2],noise_2000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[8][4],noise_2000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[8][6],noise_2000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[8][8],noise_2000_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(noisecurves[10][2],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[10][4],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[10][6],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[10][8],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    memcpy(noisecurves[12][2],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[12][4],noise_4000_60dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[12][6],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX);
+    memcpy(noisecurves[12][8],noise_4000_80dB_SL,sizeof(float)*EHMER_MAX);
+
+    setup_curve(tonecurves[0],0,vi.toneatt_125Hz);
+    setup_curve(tonecurves[2],2,vi.toneatt_250Hz);
+    setup_curve(tonecurves[4],4,vi.toneatt_500Hz);
+    setup_curve(tonecurves[6],6,vi.toneatt_1000Hz);
+    setup_curve(tonecurves[8],8,vi.toneatt_2000Hz);
+    setup_curve(tonecurves[10],10,vi.toneatt_4000Hz);
+    setup_curve(tonecurves[12],12,vi.toneatt_8000Hz);
+    
+    setup_curve(noisecurves[0],0,vi.noiseatt_125Hz);
+    setup_curve(noisecurves[2],2,vi.noiseatt_250Hz);
+    setup_curve(noisecurves[4],4,vi.noiseatt_500Hz);
+    setup_curve(noisecurves[6],6,vi.noiseatt_1000Hz);
+    setup_curve(noisecurves[8],8,vi.noiseatt_2000Hz);
+    setup_curve(noisecurves[10],10,vi.noiseatt_4000Hz);
+    setup_curve(noisecurves[12],12,vi.noiseatt_8000Hz);
+
+    for(i=1;i<13;i+=2){
+      for(j=0;j<9;j++){
+       interp_curve_dB(tonecurves[i][j],
+                       tonecurves[i-1][j],
+                       tonecurves[i+1][j],.5);
+       interp_curve_dB(noisecurves[i][j],
+                       noisecurves[i-1][j],
+                       noisecurves[i+1][j],.5);
+      }
+    }
+    for(i=0;i<5;i++){
+      peakatt[0][i]=fromdB(vi.peakatt_125Hz[i]);
+      peakatt[1][i]=fromdB(vi.peakatt_250Hz[i]);
+      peakatt[2][i]=fromdB(vi.peakatt_500Hz[i]);
+      peakatt[3][i]=fromdB(vi.peakatt_1000Hz[i]);
+      peakatt[4][i]=fromdB(vi.peakatt_2000Hz[i]);
+      peakatt[5][i]=fromdB(vi.peakatt_4000Hz[i]);
+      peakatt[6][i]=fromdB(vi.peakatt_8000Hz[i]);
+    }
+  */
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Residue0.java b/jorbis/src/com/jcraft/jorbis/Residue0.java
new file mode 100644 (file)
index 0000000..be42518
--- /dev/null
@@ -0,0 +1,454 @@
+/* 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 Residue0 extends FuncResidue{
+  void pack(Object vr, Buffer opb){
+    InfoResidue0 info=(InfoResidue0)vr;
+    int acc=0;
+    opb.write(info.begin,24);
+    opb.write(info.end,24);
+
+    opb.write(info.grouping-1,24);  /* residue vectors to group and 
+                                    code with a partitioned book */
+    opb.write(info.partitions-1,6); /* possible partition choices */
+    opb.write(info.groupbook,8);  /* group huffman book */
+
+    /* secondstages is a bitmask; as encoding progresses pass by pass, a
+       bitmask of one indicates this partition class has bits to write
+       this pass */
+    for(int j=0;j<info.partitions;j++){
+      if(ilog(info.secondstages[j])>3){
+      /* yes, this is a minor hack due to not thinking ahead */
+        opb.write(info.secondstages[j],3); 
+        opb.write(1,1);
+        opb.write(info.secondstages[j]>>>3,5); 
+      }
+      else{
+        opb.write(info.secondstages[j],4); /* trailing zero */
+      }
+      acc+=icount(info.secondstages[j]);
+   }
+    for(int j=0;j<acc;j++){
+      opb.write(info.booklist[j],8);
+    }
+  }
+
+  Object unpack(Info vi, Buffer opb){
+    int acc=0;
+    InfoResidue0 info=new InfoResidue0();
+
+    info.begin=opb.read(24);
+    info.end=opb.read(24);
+    info.grouping=opb.read(24)+1;
+    info.partitions=opb.read(6)+1;
+    info.groupbook=opb.read(8);
+
+    for(int j=0;j<info.partitions;j++){
+      int cascade=opb.read(3);
+      if(opb.read(1)!=0){
+        cascade|=(opb.read(5)<<3);
+      }
+      info.secondstages[j]=cascade;
+      acc+=icount(cascade);
+    }
+
+    for(int j=0;j<acc;j++){
+      info.booklist[j]=opb.read(8);
+//    if(info.booklist[j]==255)info.booklist[j]=-1;
+    }
+
+    if(info.groupbook>=vi.books){
+      free_info(info);
+      return(null);
+    }
+
+    for(int j=0;j<acc;j++){
+      if(info.booklist[j]>=vi.books){
+       free_info(info);
+       return(null);
+      }
+    }
+    return(info);
+//  errout:
+//    free_info(info);
+//    return(NULL);
+  }
+
+  Object look(DspState vd, InfoMode vm, Object vr){
+    InfoResidue0 info=(InfoResidue0)vr;
+    LookResidue0 look=new LookResidue0();
+    int acc=0;
+    int dim;
+    int maxstage=0;
+    look.info=info;
+    look.map=vm.mapping;
+
+    look.parts=info.partitions;
+    look.fullbooks=vd.fullbooks;
+    look.phrasebook=vd.fullbooks[info.groupbook];
+
+    dim=look.phrasebook.dim;
+
+    look.partbooks=new int[look.parts][];
+
+    for(int j=0;j<look.parts;j++){
+      int stages=ilog(info.secondstages[j]);
+      if(stages!=0){
+        if(stages>maxstage)maxstage=stages;
+        look.partbooks[j]=new int[stages];
+        for(int k=0; k<stages; k++){
+          if((info.secondstages[j]&(1<<k))!=0){
+            look.partbooks[j][k]=info.booklist[acc++];
+         }
+       }
+      }
+    }
+
+    look.partvals=(int)Math.rint(Math.pow(look.parts,dim));
+    look.stages=maxstage;
+    look.decodemap=new int[look.partvals][];
+    for(int j=0;j<look.partvals;j++){
+      int val=j;
+      int mult=look.partvals/look.parts;
+      look.decodemap[j]=new int[dim];
+
+      for(int k=0;k<dim;k++){
+        int deco=val/mult;
+        val-=deco*mult;
+        mult/=look.parts;
+        look.decodemap[j][k]=deco;
+      }
+    }
+    return(look);
+  }
+  void free_info(Object i){}
+  void free_look(Object i){}
+  int forward(Block vb,Object vl, float[][] in, int ch){
+    System.err.println("Residue0.forward: not implemented");
+    return 0;
+  }
+
+  static int[][][] partword=new int[2][][]; // _01inverse is synchronized for
+                                            // re-using partword
+  synchronized static int _01inverse(Block vb, Object vl, 
+                                    float[][] in,int ch,int decodepart){
+    int i,j,k,l,s;
+    LookResidue0 look=(LookResidue0 )vl;
+    InfoResidue0 info=look.info;
+
+    // move all this setup out later
+    int samples_per_partition=info.grouping;
+    int partitions_per_word=look.phrasebook.dim;
+    int n=info.end-info.begin;
+  
+    int partvals=n/samples_per_partition;
+    int partwords=(partvals+partitions_per_word-1)/partitions_per_word;
+
+    if(partword.length<ch){
+      partword=new int[ch][][];
+      for(j=0;j<ch;j++){
+        partword[j]=new int[partwords][];
+      }
+    }
+    else{
+      for(j=0;j<ch;j++){
+        if(partword[j]==null || partword[j].length<partwords)
+         partword[j]=new int[partwords][];
+      }
+    }
+
+    for(s=0;s<look.stages;s++){
+      // each loop decodes on partition codeword containing 
+      // partitions_pre_word partitions
+      for(i=0,l=0;i<partvals;l++){
+        if(s==0){
+         // fetch the partition word for each channel
+         for(j=0;j<ch;j++){
+           int temp=look.phrasebook.decode(vb.opb);
+           if(temp==-1){
+             //goto eopbreak;
+             return(0);
+           }
+           partword[j][l]=look.decodemap[temp];
+           if(partword[j][l]==null){
+//           goto errout;
+             return(0);
+           }
+         } 
+        }
+      
+        // now we decode residual values for the partitions
+        for(k=0;k<partitions_per_word && i<partvals;k++,i++)
+         for(j=0;j<ch;j++){
+           int offset=info.begin+i*samples_per_partition;
+           if((info.secondstages[partword[j][l][k]]&(1<<s))!=0){
+             CodeBook stagebook=look.fullbooks[look.partbooks[partword[j][l][k]][s]];
+//           CodeBook stagebook=look.partbooks[partword[j][l][k]][s];
+             if(stagebook!=null){
+                 if(decodepart==0){
+                   if(stagebook.decodevs_add(in[j],offset,vb.opb,samples_per_partition)==-1){
+                     // goto errout;
+                     return(0);
+                   }
+                 }
+                  else if(decodepart==1){
+                   if(stagebook.decodev_add(in[j], offset, vb.opb,samples_per_partition)==-1){
+                     // goto errout;
+                     return(0);
+                   }
+                 }
+             }
+           }
+         }
+      } 
+    }
+//  errout:
+//  eopbreak:
+  return(0);
+  }
+
+  static int _2inverse(Block vb, Object vl, float[][] in, int ch){
+    int i,j,k,l,s;
+    LookResidue0 look=(LookResidue0 )vl;
+    InfoResidue0 info=look.info;
+
+    // move all this setup out later
+    int samples_per_partition=info.grouping;
+    int partitions_per_word=look.phrasebook.dim;
+    int n=info.end-info.begin;
+  
+    int partvals=n/samples_per_partition;
+    int partwords=(partvals+partitions_per_word-1)/partitions_per_word;
+
+    int[][] partword=new int[partwords][];
+    for(s=0;s<look.stages;s++){
+      for(i=0,l=0;i<partvals;l++){
+        if(s==0){
+          // fetch the partition word for each channel
+         int temp=look.phrasebook.decode(vb.opb);
+         if(temp==-1){
+            // goto eopbreak;
+           return(0);
+         }
+         partword[l]=look.decodemap[temp];
+         if(partword[l]==null){
+           // goto errout;
+           return(0);
+         }
+       }
+
+        // now we decode residual values for the partitions
+        for(k=0;k<partitions_per_word && i<partvals;k++,i++){
+          int offset=info.begin+i*samples_per_partition;
+         if((info.secondstages[partword[l][k]]&(1<<s))!=0){
+            CodeBook stagebook=look.fullbooks[look.partbooks[partword[l][k]][s]];
+           if(stagebook!=null){
+              if(stagebook.decodevv_add(in, offset, ch, vb.opb,samples_per_partition)==-1){
+                // goto errout;
+               return(0);
+             }
+           }
+         } 
+       }
+      }
+    }
+//  errout:
+//  eopbreak:
+    return(0);
+  }
+
+  int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){
+    //System.err.println("Residue0.inverse");
+    int used=0;
+    for(int i=0;i<ch;i++){
+      if(nonzero[i]!=0){
+        in[used++]=in[i];
+      }
+    }
+    if(used!=0)
+      return(_01inverse(vb,vl,in,used,0));
+    else
+      return(0);
+  }
+
+/*
+  int inverse(Block vb, Object vl, float[][] in, int ch){
+//System.err.println("Residue0.inverse");
+    int i,j,k,l,transend=vb.pcmend/2;
+    LookResidue0 look=(LookResidue0 )vl;
+    InfoResidue0 info=look.info;
+
+    // move all this setup out later
+    int samples_per_partition=info.grouping;
+    int partitions_per_word=look.phrasebook.dim;
+    int n=info.end-info.begin;
+
+    int partvals=n/samples_per_partition;
+    int partwords=(partvals+partitions_per_word-1)/partitions_per_word;
+    int[][] partword=new int[ch][];
+    float[] work=new float[samples_per_partition];
+    partvals=partwords*partitions_per_word;
+
+    // make sure we're zeroed up to the start
+    for(j=0;j<ch;j++){
+      for(k=0; k<info.begin; k++)in[j][k]=0.0f;
+    }
+
+    for(i=info.begin,l=0;i<info.end;){
+      // fetch the partition word for each channel
+      for(j=0;j<ch;j++){
+        int temp=look.phrasebook.decode(vb.opb);
+        if(temp==-1){
+          //goto eopbreak;
+          if(i<transend){
+            for(j=0;j<ch;j++){
+             for(k=0;k<transend-i;k++)in[j][i+k]=0.0f;
+            }
+         }
+         return(0);
+       }
+        partword[j]=look.decodemap[temp];
+        if(partword[j]==null){
+          //goto errout;
+          for(j=0;j<ch;j++){
+            for(k=0;k<transend;k++)in[j][k]=0.0f;
+         }
+         return(0);
+       }
+      }
+    
+      // now we decode interleaved residual values for the partitions
+      for(k=0;k<partitions_per_word;k++,l++,i+=samples_per_partition){
+       for(j=0;j<ch;j++){
+         int part=partword[j][k];
+         if(decodepart(vb.opb,work, in[j], i,samples_per_partition,
+                       info.secondstages[part],
+                       look.partbooks[part])==-1){
+           //goto eopbreak;
+           if(i<transend){
+             for(j=0;j<ch;j++){
+               for(k=0;k<transend-i;k++)in[j][i+k]=0.0f;
+             }
+           }
+           return(0);
+         }
+       }
+      }
+    }
+
+// eopbreak:
+    if(i<transend){
+      for(j=0;j<ch;j++){
+        for(k=0;k<transend-i;k++)in[j][i+k]=0.0f;
+      }
+    }
+    return(0);
+
+// errout:
+//  for(j=0;j<ch;j++)
+//    for(k=0;k<transend;k++)in[j][k]=0.0f;
+//  return(0);
+  }
+  int decodepart(Buffer opb, float[] work, float[] vec, int veci,
+                int n, int stages, CodeBook[] books){
+    int i,j;
+    for(i=0;i<n;i++)work[i]=0.0f;
+
+    for(j=0;j<stages;j++){
+      int dim=books[j].dim;
+      int step=n/dim;
+      for(i=0;i<step;i++){
+        if(books[j].decodevs(work, i, opb, step, 0)==-1){
+         return(-1);
+       }
+      }
+    }
+    for(i=0;i<n;i++){
+      vec[veci+i]*=work[i];
+    }
+    return(0);
+  }
+*/
+
+  private static int ilog(int v){
+    int ret=0;
+    while(v!=0){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+  private static int icount(int v){
+    int ret=0;
+   while(v!=0){
+      ret+=(v&1);
+      v>>>=1;
+    }
+    return(ret);
+  }
+}
+
+class LookResidue0 {
+  InfoResidue0 info;
+  int map;
+  
+  int parts;
+  int stages;
+  CodeBook[] fullbooks;
+  CodeBook   phrasebook;
+  int[][] partbooks;
+//  CodeBook[][] partbooks;
+
+  int partvals;
+  int[][] decodemap;
+
+  int  postbits;
+  int         phrasebits;
+//  int[][]     frames;
+  int    frames;
+}
+
+class InfoResidue0{
+  // block-partitioned VQ coded straight residue
+  int begin;
+  int end;
+
+  // first stage (lossless partitioning)
+  int grouping;                   // group n vectors per partition
+  int partitions;                 // possible codebooks for a partition
+  int groupbook;                  // huffbook for partitioning
+  int[] secondstages=new int[64]; // expanded out to pointers in lookup
+  int[] booklist=new int[256];    // list of second stage books
+
+  // encode-only heuristic settings
+  float[] entmax=new float[64];       // book entropy threshholds
+  float[] ampmax=new float[64];       // book amp threshholds
+  int[] subgrp=new int[64];       // book heuristic subgroup size
+  int[] blimit=new int[64];       // subgroup position limits
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Residue1.java b/jorbis/src/com/jcraft/jorbis/Residue1.java
new file mode 100644 (file)
index 0000000..c29ed8d
--- /dev/null
@@ -0,0 +1,51 @@
+/* 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 Residue1 extends Residue0{
+  int forward(Block vb,Object vl, float[][] in, int ch){
+    System.err.println("Residue0.forward: not implemented");
+    return 0;
+  }
+
+  int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){
+//System.err.println("Residue0.inverse");
+    int used=0;
+    for(int i=0; i<ch; i++){
+      if(nonzero[i]!=0){
+        in[used++]=in[i];
+      }
+    }
+    if(used!=0){
+      return(_01inverse(vb,vl,in,used,1));
+    }
+    else{
+      return 0;
+    }
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/Residue2.java b/jorbis/src/com/jcraft/jorbis/Residue2.java
new file mode 100644 (file)
index 0000000..146a834
--- /dev/null
@@ -0,0 +1,44 @@
+/* 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 Residue2 extends Residue0{
+  int forward(Block vb,Object vl, float[][] in, int ch){
+    System.err.println("Residue0.forward: not implemented");
+    return 0;
+  }
+
+  int inverse(Block vb, Object vl, float[][] in, int[] nonzero, int ch){
+//System.err.println("Residue0.inverse");
+    int i=0;
+    for(i=0;i<ch;i++)if(nonzero[i]!=0)break;
+    if(i==ch)return(0); /* no nonzero vectors */
+
+    return(_2inverse(vb,vl,in, ch));
+  }
+}
diff --git a/jorbis/src/com/jcraft/jorbis/StaticCodeBook.java b/jorbis/src/com/jcraft/jorbis/StaticCodeBook.java
new file mode 100644 (file)
index 0000000..7d9d6dc
--- /dev/null
@@ -0,0 +1,588 @@
+/* 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 StaticCodeBook{
+  int   dim;            // codebook dimensions (elements per vector)
+  int   entries;        // codebook entries
+  int[] lengthlist;     // codeword lengths in bits
+
+  // mapping
+  int   maptype;        // 0=none
+                       // 1=implicitly populated values from map column 
+                       // 2=listed arbitrary values
+
+  // The below does a linear, single monotonic sequence mapping.
+  int   q_min;       // packed 32 bit float; quant value 0 maps to minval
+  int   q_delta;     // packed 32 bit float; val 1 - val 0 == delta
+  int   q_quant;     // bits: 0 < quant <= 16
+  int   q_sequencep; // bitflag
+
+  // additional information for log (dB) mapping; the linear mapping
+  // is assumed to actually be values in dB.  encodebias is used to
+  // assign an error weight to 0 dB. We have two additional flags:
+  // zeroflag indicates if entry zero is to represent -Inf dB; negflag
+  // indicates if we're to represent negative linear values in a
+  // mirror of the positive mapping.
+
+  int[] quantlist;  // map == 1: (int)(entries/dim) element column map
+                    // map == 2: list of dim*entries quantized entry vals
+
+  // encode helpers
+  EncodeAuxNearestMatch nearest_tree;
+  EncodeAuxThreshMatch  thresh_tree;
+
+  StaticCodeBook(){}
+  StaticCodeBook(int dim, int entries, int[] lengthlist,
+                int maptype, int q_min, int q_delta, 
+                int q_quant, int q_sequencep, int[] quantlist,
+                //EncodeAuxNearestmatch nearest_tree,
+                Object nearest_tree,
+                // EncodeAuxThreshmatch thresh_tree,
+                Object thresh_tree
+                ){
+    this();
+    this.dim=dim; this.entries=entries; this.lengthlist=lengthlist;
+    this.maptype=maptype; this.q_min=q_min; this.q_delta=q_delta;
+    this.q_quant=q_quant; this.q_sequencep=q_sequencep; 
+    this.quantlist=quantlist;
+  }
+
+  int pack(Buffer opb){
+    int i;
+    boolean ordered=false;
+
+    opb.write(0x564342,24);
+    opb.write(dim, 16);
+    opb.write(entries, 24);
+
+    // pack the codewords.  There are two packings; length ordered and
+    // length random.  Decide between the two now.
+  
+    for(i=1;i<entries;i++){
+      if(lengthlist[i]<lengthlist[i-1])break;
+    }
+    if(i==entries)ordered=true;
+  
+    if(ordered){
+      // length ordered.  We only need to say how many codewords of
+      // each length.  The actual codewords are generated
+      // deterministically
+
+      int count=0;
+      opb.write(1,1);               // ordered
+      opb.write(lengthlist[0]-1,5); // 1 to 32
+
+      for(i=1;i<entries;i++){
+       int _this=lengthlist[i];
+       int _last=lengthlist[i-1];
+       if(_this>_last){
+         for(int j=_last;j<_this;j++){
+           opb.write(i-count,ilog(entries-count));
+           count=i;
+         }
+       }
+      }
+      opb.write(i-count,ilog(entries-count));
+    }
+    else{
+      // length random.  Again, we don't code the codeword itself, just
+      // the length.  This time, though, we have to encode each length
+      opb.write(0,1);   // unordered
+    
+      // algortihmic mapping has use for 'unused entries', which we tag
+      // here.  The algorithmic mapping happens as usual, but the unused
+      // entry has no codeword.
+      for(i=0;i<entries;i++){
+       if(lengthlist[i]==0)break;
+      }
+
+      if(i==entries){
+       opb.write(0,1); // no unused entries
+       for(i=0;i<entries;i++){
+         opb.write(lengthlist[i]-1,5);
+       }
+      }
+      else{
+       opb.write(1,1); // we have unused entries; thus we tag
+       for(i=0;i<entries;i++){
+         if(lengthlist[i]==0){
+           opb.write(0,1);
+         }
+         else{
+           opb.write(1,1);
+           opb.write(lengthlist[i]-1,5);
+         }
+       }
+      }
+    }
+
+    // is the entry number the desired return value, or do we have a
+    // mapping? If we have a mapping, what type?
+    opb.write(maptype,4);
+    switch(maptype){
+    case 0:
+      // no mapping
+      break;
+    case 1:
+    case 2:
+      // implicitly populated value mapping
+      // explicitly populated value mapping
+      if(quantlist==null){
+       // no quantlist?  error
+       return(-1);
+      }
+    
+      // values that define the dequantization
+      opb.write(q_min,32);
+      opb.write(q_delta,32);
+      opb.write(q_quant-1,4);
+      opb.write(q_sequencep,1);
+    
+      {
+       int quantvals=0;
+       switch(maptype){
+       case 1:
+         // a single column of (c->entries/c->dim) quantized values for
+         // building a full value list algorithmically (square lattice)
+         quantvals=maptype1_quantvals();
+         break;
+       case 2:
+         // every value (c->entries*c->dim total) specified explicitly
+         quantvals=entries*dim;
+         break;
+       }
+
+       // quantized values
+       for(i=0;i<quantvals;i++){
+         opb.write(Math.abs(quantlist[i]),q_quant);
+       }
+      }
+      break;
+    default:
+      // error case; we don't have any other map types now
+      return(-1);
+    }
+    return(0);
+  }
+/*
+*/
+
+  // unpacks a codebook from the packet buffer into the codebook struct,
+  // readies the codebook auxiliary structures for decode
+  int unpack(Buffer opb){
+    int i;
+    //memset(s,0,sizeof(static_codebook));
+
+    // make sure alignment is correct
+    if(opb.read(24)!=0x564342){
+//    goto _eofout;
+      clear();
+      return(-1); 
+    }
+
+    // first the basic parameters
+    dim=opb.read(16);
+    entries=opb.read(24);
+    if(entries==-1){
+//    goto _eofout;
+      clear();
+      return(-1); 
+    }
+
+    // codeword ordering.... length ordered or unordered?
+    switch(opb.read(1)){
+    case 0:
+      // unordered
+      lengthlist=new int[entries];
+
+      // allocated but unused entries?
+      if(opb.read(1)!=0){
+       // yes, unused entries
+
+       for(i=0;i<entries;i++){
+         if(opb.read(1)!=0){
+           int num=opb.read(5);
+           if(num==-1){
+//            goto _eofout;
+             clear();
+             return(-1); 
+           }
+           lengthlist[i]=num+1;
+         }
+         else{
+           lengthlist[i]=0;
+         }
+       }
+      }
+      else{
+       // all entries used; no tagging
+       for(i=0;i<entries;i++){
+         int num=opb.read(5);
+         if(num==-1){
+//          goto _eofout;
+           clear();
+           return(-1); 
+         }
+         lengthlist[i]=num+1;
+       }
+      }
+      break;
+    case 1:
+      // ordered
+      {
+       int length=opb.read(5)+1;
+       lengthlist=new int[entries];
+
+       for(i=0;i<entries;){
+         int num=opb.read(ilog(entries-i));
+         if(num==-1){
+//          goto _eofout;
+           clear();
+           return(-1); 
+         }
+         for(int j=0;j<num;j++,i++){
+           lengthlist[i]=length;
+         }
+         length++;
+       }
+      }
+      break;
+    default:
+      // EOF
+      return(-1);
+    }
+  
+    // Do we have a mapping to unpack?
+    switch((maptype=opb.read(4))){
+    case 0:
+      // no mapping
+      break;
+    case 1:
+    case 2:
+      // implicitly populated value mapping
+      // explicitly populated value mapping
+      q_min=opb.read(32);
+      q_delta=opb.read(32);
+      q_quant=opb.read(4)+1;
+      q_sequencep=opb.read(1);
+
+      {
+       int quantvals=0;
+       switch(maptype){
+       case 1:
+         quantvals=maptype1_quantvals();
+         break;
+       case 2:
+         quantvals=entries*dim;
+         break;
+       }
+      
+       // quantized values
+       quantlist=new int[quantvals];
+       for(i=0;i<quantvals;i++){
+         quantlist[i]=opb.read(q_quant);
+       }
+       if(quantlist[quantvals-1]==-1){
+//        goto _eofout;
+         clear();
+         return(-1); 
+       }
+      }
+      break;
+    default:
+//    goto _eofout;
+      clear();
+      return(-1); 
+    }
+    // all set
+    return(0);
+//    _errout:
+//    _eofout:
+//    vorbis_staticbook_clear(s);
+//    return(-1); 
+  }
+
+  // there might be a straightforward one-line way to do the below
+  // that's portable and totally safe against roundoff, but I haven't
+  // thought of it.  Therefore, we opt on the side of caution
+  private int maptype1_quantvals(){
+    int vals=(int)(Math.floor(Math.pow(entries,1./dim)));
+
+    // the above *should* be reliable, but we'll not assume that FP is
+    // ever reliable when bitstream sync is at stake; verify via integer
+    // means that vals really is the greatest value of dim for which
+    // vals^b->bim <= b->entries
+    // treat the above as an initial guess
+    while(true){
+      int acc=1;
+      int acc1=1;
+      for(int i=0;i<dim;i++){
+       acc*=vals;
+       acc1*=vals+1;
+      }
+      if(acc<=entries && acc1>entries){        return(vals); }
+      else{
+       if(acc>entries){ vals--; }
+       else{ vals++; }
+      }
+    }
+  }
+    
+  void clear(){
+//  if(quantlist!=null)free(b->quantlist);
+//  if(lengthlist!=null)free(b->lengthlist);
+//  if(nearest_tree!=null){
+//    free(b->nearest_tree->ptr0);
+//    free(b->nearest_tree->ptr1);
+//    free(b->nearest_tree->p);
+//    free(b->nearest_tree->q);
+//    memset(b->nearest_tree,0,sizeof(encode_aux_nearestmatch));
+//    free(b->nearest_tree);
+//  }
+//  if(thresh_tree!=null){
+//    free(b->thresh_tree->quantthresh);
+//    free(b->thresh_tree->quantmap);
+//    memset(b->thresh_tree,0,sizeof(encode_aux_threshmatch));
+//    free(b->thresh_tree);
+//  }
+//  memset(b,0,sizeof(static_codebook));
+  }
+
+  // unpack the quantized list of values for encode/decode
+  // we need to deal with two map types: in map type 1, the values are
+  // generated algorithmically (each column of the vector counts through
+  // the values in the quant vector). in map type 2, all the values came
+  // in in an explicit list.  Both value lists must be unpacked
+  float[] unquantize(){
+
+    if(maptype==1 || maptype==2){
+      int quantvals;
+      float mindel=float32_unpack(q_min);
+      float delta=float32_unpack(q_delta);
+      float[] r=new float[entries*dim];
+
+       //System.err.println("q_min="+q_min+", mindel="+mindel);
+
+      // maptype 1 and 2 both use a quantized value vector, but
+      // different sizes
+      switch(maptype){
+      case 1:
+       // most of the time, entries%dimensions == 0, but we need to be
+       // well defined.  We define that the possible vales at each
+       // scalar is values == entries/dim.  If entries%dim != 0, we'll
+       // have 'too few' values (values*dim<entries), which means that
+       // we'll have 'left over' entries; left over entries use zeroed
+       // values (and are wasted).  So don't generate codebooks like that
+       quantvals=maptype1_quantvals();
+       for(int j=0;j<entries;j++){
+         float last=0.f;
+         int indexdiv=1;
+         for(int k=0;k<dim;k++){
+           int index=(j/indexdiv)%quantvals;
+           float val=quantlist[index];
+           val=Math.abs(val)*delta+mindel+last;
+           if(q_sequencep!=0)last=val;   
+           r[j*dim+k]=val;
+           indexdiv*=quantvals;
+         }
+       }
+       break;
+      case 2:
+       for(int j=0;j<entries;j++){
+         float last=0.f;
+         for(int k=0;k<dim;k++){
+           float val=quantlist[j*dim+k];
+//if((j*dim+k)==0){System.err.println(" | 0 -> "+val+" | ");}
+           val=Math.abs(val)*delta+mindel+last;
+           if(q_sequencep!=0)last=val;   
+           r[j*dim+k]=val;
+//if((j*dim+k)==0){System.err.println(" $ r[0] -> "+r[0]+" | ");}
+         }
+       }
+//System.err.println("\nr[0]="+r[0]);
+      }
+      return(r);
+    }
+    return(null);
+  }
+
+  private static int ilog(int v){
+    int ret=0;
+    while(v!=0){
+      ret++;
+      v>>>=1;
+    }
+    return(ret);
+  }
+
+  // 32 bit float (not IEEE; nonnormalized mantissa +
+  // biased exponent) : neeeeeee eeemmmmm mmmmmmmm mmmmmmmm 
+  // Why not IEEE?  It's just not that important here.
+
+  static final int VQ_FEXP=10;
+  static final int VQ_FMAN=21;
+  static final int VQ_FEXP_BIAS=768; // bias toward values smaller than 1.
+
+  // doesn't currently guard under/overflow 
+  static long float32_pack(float val){
+    int sign=0;
+    int exp;
+    int mant;
+    if(val<0){
+      sign=0x80000000;
+      val= -val;
+    }
+    exp=(int)Math.floor(Math.log(val)/Math.log(2));
+    mant=(int)Math.rint(Math.pow(val,(VQ_FMAN-1)-exp));
+    exp=(exp+VQ_FEXP_BIAS)<<VQ_FMAN;
+    return(sign|exp|mant);
+  }
+
+  static float float32_unpack(int val){
+    float mant=val&0x1fffff;
+    float sign=val&0x80000000;
+    float exp =(val&0x7fe00000)>>>VQ_FMAN;
+//System.err.println("mant="+mant+", sign="+sign+", exp="+exp);
+    //if(sign!=0.0)mant= -mant;
+    if((val&0x80000000)!=0)mant= -mant;
+//System.err.println("mant="+mant);
+    return(ldexp(mant,((int)exp)-(VQ_FMAN-1)-VQ_FEXP_BIAS));
+  }
+
+  static float ldexp(float foo, int e){
+    return (float)(foo*Math.pow(2, e));
+  }
+
+/*
+  // TEST
+  // Unit tests of the dequantizer; this stuff will be OK
+  // cross-platform, I simply want to be sure that special mapping cases
+  // actually work properly; a bug could go unnoticed for a while
+
+  // cases:
+  //
+  // no mapping
+  // full, explicit mapping
+  // algorithmic mapping
+  //
+  // nonsequential
+  // sequential
+
+  static int[] full_quantlist1={0,1,2,3, 4,5,6,7, 8,3,6,1};
+  static int[] partial_quantlist1={0,7,2};
+
+  // no mapping
+  static StaticCodeBook test1=new StaticCodeBook(4,16,null,
+                                                0,0,0,0,0,
+                                                null,null,null);
+  static float[] test1_result=null;
+  
+  // linear, full mapping, nonsequential
+  static StaticCodeBook test2=new StaticCodeBook(4,3,null,
+                                                2,-533200896,1611661312,4,0,
+                                                full_quantlist1, null, null);
+  static float[] test2_result={-3,-2,-1,0, 1,2,3,4, 5,0,3,-2};
+
+  // linear, full mapping, sequential
+  static StaticCodeBook test3=new StaticCodeBook(4,3,null,
+                                                2, -533200896,1611661312,4,1,
+                                                full_quantlist1,null, null);
+  static float[] test3_result={-3,-5,-6,-6, 1,3,6,10, 5,5,8,6};
+
+  // linear, algorithmic mapping, nonsequential
+  static StaticCodeBook test4=new StaticCodeBook(3,27,null,
+                                                1,-533200896,1611661312,4,0,
+                                                partial_quantlist1,null,null);
+  static float[] test4_result={-3,-3,-3, 4,-3,-3, -1,-3,-3,
+                               -3, 4,-3, 4, 4,-3, -1, 4,-3,
+                               -3,-1,-3, 4,-1,-3, -1,-1,-3, 
+                               -3,-3, 4, 4,-3, 4, -1,-3, 4,
+                               -3, 4, 4, 4, 4, 4, -1, 4, 4,
+                               -3,-1, 4, 4,-1, 4, -1,-1, 4,
+                               -3,-3,-1, 4,-3,-1, -1,-3,-1,
+                               -3, 4,-1, 4, 4,-1, -1, 4,-1,
+                               -3,-1,-1, 4,-1,-1, -1,-1,-1};
+
+  // linear, algorithmic mapping, sequential
+  static StaticCodeBook test5=new StaticCodeBook(3,27,null,
+                                                1,-533200896,1611661312,4,1,
+                                                partial_quantlist1,null,null);
+  static float[] test5_result={-3,-6,-9, 4, 1,-2, -1,-4,-7,
+                               -3, 1,-2, 4, 8, 5, -1, 3, 0,
+                               -3,-4,-7, 4, 3, 0, -1,-2,-5, 
+                               -3,-6,-2, 4, 1, 5, -1,-4, 0,
+                               -3, 1, 5, 4, 8,12, -1, 3, 7,
+                               -3,-4, 0, 4, 3, 7, -1,-2, 2,
+                               -3,-6,-7, 4, 1, 0, -1,-4,-5,
+                               -3, 1, 0, 4, 8, 7, -1, 3, 2,
+                               -3,-4,-5, 4, 3, 2, -1,-2,-3};
+
+  void run_test(float[] comp){
+    float[] out=unquantize();
+    if(comp!=null){
+      if(out==null){
+       System.err.println("_book_unquantize incorrectly returned NULL");
+       System.exit(1);
+      }
+      for(int i=0;i<entries*dim;i++){
+       if(Math.abs(out[i]-comp[i])>.0001){
+         System.err.println("disagreement in unquantized and reference data:\nposition "+i+": "+out[i]+" != "+comp[i]);
+         System.exit(1);
+       }
+      }
+    }
+    else{
+      if(out!=null){
+       System.err.println("_book_unquantize returned a value array:\n  correct result should have been NULL");
+       System.exit(1);
+      }
+    }
+  }
+
+  public static void main(String[] arg){
+    // run the nine dequant tests, and compare to the hand-rolled results
+    System.err.print("Dequant test 1... ");
+    test1.run_test(test1_result);
+    System.err.print("OK\nDequant test 2... ");
+    test2.run_test(test2_result);
+    System.err.print("OK\nDequant test 3... ");
+    test3.run_test(test3_result);
+    System.err.print("OK\nDequant test 4... ");
+    test4.run_test(test4_result);
+    System.err.print("OK\nDequant test 5... ");
+    test5.run_test(test5_result);
+    System.err.print("OK\n\n");
+  }
+*/
+}
+
+
+
+
+
diff --git a/jorbis/src/com/jcraft/jorbis/Time0.java b/jorbis/src/com/jcraft/jorbis/Time0.java
new file mode 100644 (file)
index 0000000..f6a9fcb
--- /dev/null
@@ -0,0 +1,38 @@
+/* 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 Time0 extends FuncTime{
+  void pack(Object i, Buffer opb){}
+  Object unpack(Info vi , Buffer opb){return "";}
+  Object look(DspState vd, InfoMode mi, Object i){return "";}
+  void free_info(Object i){}
+  void free_look(Object i){}
+  int forward(Block vb, Object i){return 0;}
+  int inverse(Block vb, Object i, float[] in, float[] out){return 0;}
+}
diff --git a/jorbis/src/com/jcraft/jorbis/VorbisFile.java b/jorbis/src/com/jcraft/jorbis/VorbisFile.java
new file mode 100644 (file)
index 0000000..64edff0
--- /dev/null
@@ -0,0 +1,1361 @@
+/* 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.*;
+
+import java.io.InputStream;
+import java.io.IOException;
+
+public class VorbisFile{
+  static final int CHUNKSIZE=8500;
+  static final int SEEK_SET=0;
+  static final int SEEK_CUR=1;
+  static final int SEEK_END=2;
+
+  static final int OV_FALSE=-1;
+  static final int OV_EOF=-2;
+  static final int OV_HOLE=-3;
+
+  static final int OV_EREAD=-128;
+  static final int OV_EFAULT=-129;
+  static final int OV_EIMPL=-130;
+  static final int OV_EINVAL=-131;
+  static final int OV_ENOTVORBIS=-132;
+  static final int OV_EBADHEADER=-133;
+  static final int OV_EVERSION=-134;
+  static final int OV_ENOTAUDIO=-135;
+  static final int OV_EBADPACKET=-136;
+  static final int OV_EBADLINK=-137;
+  static final int OV_ENOSEEK=-138;
+
+  InputStream datasource;
+  boolean seekable=false;
+  long offset;
+  long end;
+  
+  SyncState oy=new SyncState();
+
+  int links;
+  long[] offsets;
+  long[] dataoffsets;
+  int[] serialnos;
+  long[] pcmlengths;
+  Info[] vi;
+  Comment[] vc;
+
+  // Decoding working state local storage
+  long pcm_offset;
+  boolean decode_ready=false;
+  int current_serialno;
+  int current_link;
+
+  float bittrack;
+  float samptrack;
+
+  StreamState os=new StreamState(); // take physical pages, weld into a logical
+                                    // stream of packets
+  DspState vd=new DspState(); // central working state for 
+                              // the packet->PCM decoder
+  Block vb=new Block(vd);     // local working space for packet->PCM decode
+
+  //ov_callbacks callbacks;
+
+  public VorbisFile(String file) throws JOrbisException {
+    super();
+    InputStream is=null;
+    try{ 
+      is=new SeekableInputStream(file);
+      int ret=open(is, null, 0);
+      if(ret==-1){
+        throw new JOrbisException("VorbisFile: open return -1");
+      }
+    }
+    catch(Exception e){
+      throw new JOrbisException("VorbisFile: "+e.toString());
+    }
+    finally{
+      if(is != null){
+        try {
+          is.close();
+        }
+        catch (IOException e) {
+          e.printStackTrace();
+        }
+      }
+    }
+  }
+
+  public VorbisFile(InputStream is, byte[] initial, int ibytes)
+    throws JOrbisException {
+    super();
+    int ret=open(is, initial, ibytes);
+    if(ret==-1){
+    }
+  }
+
+  private int get_data(){
+    int index=oy.buffer(CHUNKSIZE);
+    byte[] buffer=oy.data;
+//  int bytes=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource);
+    int bytes=0;
+    try{
+      bytes=datasource.read(buffer, index, CHUNKSIZE);
+    }
+    catch(Exception e){
+      //System.err.println(e);
+      return OV_EREAD;
+    }
+    oy.wrote(bytes);
+    if(bytes==-1){
+//      System.out.println("bytes="+bytes);
+      bytes=0;
+    }
+    return bytes;
+  }
+
+  private void seek_helper(long offst){
+    //callbacks.seek_func(datasource, offst, SEEK_SET);
+    fseek(datasource, offst, SEEK_SET);
+    this.offset=offst;
+    oy.reset();
+  }
+
+  private int get_next_page(Page page, long boundary){
+    if(boundary>0) boundary+=offset;
+    while(true){
+      int more;
+      if(boundary>0 && offset>=boundary)return OV_FALSE;
+      more=oy.pageseek(page);
+      if(more<0){offset-=more;}
+      else{
+       if(more==0){
+         if(boundary==0)return OV_FALSE;
+//       if(get_data()<=0)return -1;
+          int ret=get_data();
+          if(ret==0) return OV_EOF;
+          if(ret<0) return OV_EREAD; 
+       }
+       else{
+         int ret=(int)offset; //!!!
+         offset+=more;
+         return ret;
+       }
+      }
+    }
+  }
+
+  private int get_prev_page(Page page) throws JOrbisException {
+    long begin=offset; //!!!
+    int ret;
+    int offst=-1;
+    while(offst==-1){
+      begin-=CHUNKSIZE;
+      if(begin<0)
+        begin=0;
+      seek_helper(begin);
+      while(offset<begin+CHUNKSIZE){
+       ret=get_next_page(page, begin+CHUNKSIZE-offset);
+       if(ret==OV_EREAD){ return OV_EREAD; }
+       if(ret<0){ 
+          if(offst == -1)
+            throw new JOrbisException();
+          break; 
+        }
+       else{ offst=ret; }
+      }
+    }
+    seek_helper(offst); //!!!
+    ret=get_next_page(page, CHUNKSIZE);
+    if(ret<0){
+      //System.err.println("Missed page fencepost at end of logical bitstream Exiting");
+      //System.exit(1);
+      return OV_EFAULT;
+    }
+    return offst;
+  }
+
+  int bisect_forward_serialno(long begin, long searched, long end, int currentno, int m){
+    long endsearched=end;
+    long next=end;
+    Page page=new Page();
+    int ret;
+
+    while(searched<endsearched){
+      long bisect;
+      if(endsearched-searched<CHUNKSIZE){
+       bisect=searched;
+      }
+      else{
+       bisect=(searched+endsearched)/2;
+      }
+
+      seek_helper(bisect);
+      ret=get_next_page(page, -1);
+      if(ret==OV_EREAD) return OV_EREAD;
+      if(ret<0 || page.serialno()!=currentno){
+       endsearched=bisect;
+       if(ret>=0)next=ret;
+      }
+      else{
+       searched=ret+page.header_len+page.body_len;
+      }
+    }
+    seek_helper(next);
+    ret=get_next_page(page, -1);
+    if(ret==OV_EREAD) return OV_EREAD;
+
+    if(searched>=end || ret==-1){
+      links=m+1;
+      offsets=new long[m+2];
+      offsets[m+1]=searched;
+    }
+    else{
+      ret=bisect_forward_serialno(next, offset, end, page.serialno(), m+1);
+      if(ret==OV_EREAD)return OV_EREAD;
+    }
+    offsets[m]=begin;
+    return 0;  
+  }
+
+  // uses the local ogg_stream storage in vf; this is important for
+  // non-streaming input sources
+  int fetch_headers(Info vi, Comment vc, int[] serialno, Page og_ptr){
+    //System.err.println("fetch_headers");
+    Page og=new Page();
+    Packet op=new Packet();
+    int ret;
+
+    if(og_ptr==null){
+      ret=get_next_page(og, CHUNKSIZE);
+      if(ret==OV_EREAD)return OV_EREAD;
+      if(ret<0) return OV_ENOTVORBIS;
+      og_ptr=og;
+    }
+  
+    if(serialno!=null)serialno[0]=og_ptr.serialno();
+
+    os.init(og_ptr.serialno());
+  
+    // extract the initial header from the first page and verify that the
+    // Ogg bitstream is in fact Vorbis data
+  
+    vi.init();
+    vc.init();
+  
+    int i=0;
+    while(i<3){
+      os.pagein(og_ptr);
+      while(i<3){
+       int result=os.packetout(op);
+       if(result==0)break;
+       if(result==-1){
+         //System.err.println("Corrupt header in logical bitstream.");
+         //goto bail_header;
+         vi.clear();
+         vc.clear();
+         os.clear();
+          return -1;
+       }
+       if(vi.synthesis_headerin(vc, op)!=0){
+         //System.err.println("Illegal header in logical bitstream.");
+         //goto bail_header;
+         vi.clear();
+         vc.clear();
+         os.clear();
+         return -1;
+       }
+       i++;
+      }
+      if(i<3)
+       if(get_next_page(og_ptr, 1)<0){
+         //System.err.println("Missing header in logical bitstream.");
+         //goto bail_header;
+         vi.clear();
+         vc.clear();
+         os.clear();
+         return -1;
+       }
+    }
+    return 0; 
+
+//  bail_header:
+//    vorbis_info_clear(vi);
+//    vorbis_comment_clear(vc);
+//    ogg_stream_clear(&vf->os);
+//    return -1;
+  }
+
+  // last step of the OggVorbis_File initialization; get all the
+  // vorbis_info structs and PCM positions.  Only called by the seekable
+  // initialization (local stream storage is hacked slightly; pay
+  // attention to how that's done)
+  void prefetch_all_headers(Info first_i,Comment first_c,
+                           int dataoffset) throws JOrbisException {
+    Page og=new Page();
+    int ret;
+  
+    vi=new Info[links];
+    vc=new Comment[links];
+    dataoffsets=new long[links];
+    pcmlengths=new long[links];
+    serialnos=new int[links];
+  
+    for(int i=0;i<links;i++){
+      if(first_i!=null && first_c!=null && i==0){
+       // we already grabbed the initial header earlier.  This just
+       // saves the waste of grabbing it again
+       // !!!!!!!!!!!!!
+       vi[i]=first_i;
+       //memcpy(vf->vi+i,first_i,sizeof(vorbis_info));
+       vc[i]=first_c;
+       //memcpy(vf->vc+i,first_c,sizeof(vorbis_comment));
+       dataoffsets[i]=dataoffset;
+      }
+      else{
+       // seek to the location of the initial header
+       seek_helper(offsets[i]); //!!!
+       vi[i]=new Info();
+       vc[i]=new Comment();
+       if(fetch_headers(vi[i], vc[i], null, null)==-1){
+         //System.err.println("Error opening logical bitstream #"+(i+1)+"\n");
+         dataoffsets[i]=-1;
+       }
+       else{
+         dataoffsets[i]=offset;
+         os.clear();
+       }
+      }
+
+      // get the serial number and PCM length of this link. To do this,
+      // get the last page of the stream
+      {
+       long end=offsets[i+1]; //!!!
+       seek_helper(end);
+
+       while(true){
+         ret=get_prev_page(og);
+         if(ret==-1){
+           // this should not be possible
+           //System.err.println("Could not find last page of logical "+
+           //                 "bitstream #"+(i)+"\n");
+           vi[i].clear();
+           vc[i].clear();
+           break;
+         }
+         if(og.granulepos()!=-1){
+           serialnos[i]=og.serialno();
+           pcmlengths[i]=og.granulepos();
+           break;
+         }
+       }
+      }
+    }
+  }
+
+  int make_decode_ready(){
+    if(decode_ready)System.exit(1);
+    vd.synthesis_init(vi[0]);
+    vb.init(vd);
+    decode_ready=true;
+    return(0);
+  }
+
+  int open_seekable() throws JOrbisException {
+    Info initial_i=new Info();
+    Comment initial_c=new Comment();
+    int serialno;
+    long end;
+    int ret;
+    int dataoffset;
+    Page og=new Page();
+    // is this even vorbis...?
+    int[] foo=new int[1];
+    ret=fetch_headers(initial_i, initial_c, foo, null);
+    serialno=foo[0];
+    dataoffset=(int)offset; //!!
+    os.clear();
+    if(ret==-1)return(-1);
+    // we can seek, so set out learning all about this file
+    seekable=true;
+    //(callbacks.seek_func)(datasource, 0, SEEK_END);
+    fseek(datasource, 0, SEEK_END);
+    //offset=end=(callbacks.tell_func)(datasource);
+    offset=ftell(datasource);
+    end=offset;
+    // We get the offset for the last page of the physical bitstream.
+    // Most OggVorbis files will contain a single logical bitstream
+    end=get_prev_page(og);
+    // moer than one logical bitstream?
+    if(og.serialno()!=serialno){
+      // Chained bitstream. Bisect-search each logical bitstream
+      // section.  Do so based on serial number only
+      if(bisect_forward_serialno(0,0,end+1,serialno,0)<0){
+        clear();
+        return OV_EREAD;
+      }
+    }
+    else{
+      // Only one logical bitstream
+      if(bisect_forward_serialno(0,end,end+1,serialno,0)<0){
+        clear();
+        return OV_EREAD;
+      }
+    }
+    prefetch_all_headers(initial_i, initial_c, dataoffset);
+    return(raw_seek(0));
+  }
+
+  int open_nonseekable(){
+    //System.err.println("open_nonseekable");
+    // we cannot seek. Set up a 'single' (current) logical bitstream entry
+    links=1;
+    vi=new Info[links]; vi[0]=new Info(); // ??
+    vc=new Comment[links]; vc[0]=new Comment(); // ?? bug?
+
+    // Try to fetch the headers, maintaining all the storage
+    int[]foo=new int[1];
+    if(fetch_headers(vi[0], vc[0], foo, null)==-1)return(-1);
+    current_serialno=foo[0];
+    make_decode_ready();
+    return 0;
+  }
+
+  // clear out the current logical bitstream decoder
+  void decode_clear(){
+    os.clear();
+    vd.clear();
+    vb.clear();
+    decode_ready=false;
+    bittrack=0.f;
+    samptrack=0.f;
+  }
+
+  // fetch and process a packet.  Handles the case where we're at a
+  // bitstream boundary and dumps the decoding machine.  If the decoding
+  // machine is unloaded, it loads it.  It also keeps pcm_offset up to
+  // date (seek and read both use this.  seek uses a special hack with
+  // readp). 
+  //
+  // return: -1) hole in the data (lost packet) 
+  //          0) need more date (only if readp==0)/eof
+  //          1) got a packet 
+
+  int process_packet(int readp){
+    Page og=new Page();
+
+    // handle one packet.  Try to fetch it from current stream state
+    // extract packets from page
+    while(true){
+      // process a packet if we can.  If the machine isn't loaded,
+      // neither is a page
+      if(decode_ready){
+       Packet op=new Packet();
+       int result=os.packetout(op);
+       long granulepos;
+        // if(result==-1)return(-1); // hole in the data. For now, swallow
+                                    // and go. We'll need to add a real
+                                    // error code in a bit.
+       if(result>0){
+         // got a packet.  process it
+         granulepos=op.granulepos;
+         if(vb.synthesis(op)==0){ // lazy check for lazy
+                                   // header handling.  The
+                                  // header packets aren't
+                                  // audio, so if/when we
+                                  // submit them,
+                                  // vorbis_synthesis will
+                                   // reject them
+           // suck in the synthesis data and track bitrate
+           {
+             int oldsamples=vd.synthesis_pcmout(null, null);
+             vd.synthesis_blockin(vb);
+             samptrack+=vd.synthesis_pcmout(null, null)-oldsamples;
+             bittrack+=op.bytes*8;
+           }
+         
+           // update the pcm offset.
+           if(granulepos!=-1 && op.e_o_s==0){
+             int link=(seekable?current_link:0);
+             int samples;
+             // this packet has a pcm_offset on it (the last packet
+             // completed on a page carries the offset) After processing
+             // (above), we know the pcm position of the *last* sample
+             // ready to be returned. Find the offset of the *first*
+             // 
+             // As an aside, this trick is inaccurate if we begin
+             // reading anew right at the last page; the end-of-stream
+             // granulepos declares the last frame in the stream, and the
+             // last packet of the last page may be a partial frame.
+             // So, we need a previous granulepos from an in-sequence page
+             // to have a reference point.  Thus the !op.e_o_s clause above
+           
+             samples=vd.synthesis_pcmout(null, null);
+             granulepos-=samples;
+             for(int i=0;i<link;i++){
+               granulepos+=pcmlengths[i];
+             }
+             pcm_offset=granulepos;
+           }
+           return(1);
+         }
+       }
+      }
+
+      if(readp==0)return(0);
+      if(get_next_page(og,-1)<0)return(0); // eof. leave unitialized
+
+      // bitrate tracking; add the header's bytes here, the body bytes
+      // are done by packet above
+      bittrack+=og.header_len*8;
+
+      // has our decoding just traversed a bitstream boundary?
+      if(decode_ready){
+       if(current_serialno!=og.serialno()){
+         decode_clear();
+       }
+      }
+
+      // Do we need to load a new machine before submitting the page?
+      // This is different in the seekable and non-seekable cases.  
+      // 
+      // In the seekable case, we already have all the header
+      // information loaded and cached; we just initialize the machine
+      // with it and continue on our merry way.
+      // 
+      // In the non-seekable (streaming) case, we'll only be at a
+      // boundary if we just left the previous logical bitstream and
+      // we're now nominally at the header of the next bitstream
+
+      if(!decode_ready){
+       int i;
+       if(seekable){
+         current_serialno=og.serialno();
+       
+         // match the serialno to bitstream section.  We use this rather than
+         // offset positions to avoid problems near logical bitstream
+         // boundaries
+         for(i=0;i<links;i++){
+           if(serialnos[i]==current_serialno)break;
+         }
+         if(i==links)return(-1); // sign of a bogus stream.  error out,
+                                 // leave machine uninitialized
+         current_link=i;
+
+         os.init(current_serialno);
+         os.reset(); 
+
+       }
+       else{
+         // we're streaming
+         // fetch the three header packets, build the info struct
+         int foo[]=new int[1];
+         int ret=fetch_headers(vi[0], vc[0], foo, og);
+          current_serialno=foo[0];
+          if(ret!=0)return ret;
+         current_link++;
+         i=0;
+       }
+       make_decode_ready();
+      }
+      os.pagein(og);
+    }
+  }
+
+  //The helpers are over; it's all toplevel interface from here on out
+  // clear out the OggVorbis_File struct
+  int clear(){
+    vb.clear();
+    vd.clear();
+    os.clear();
+    
+    if(vi!=null && links!=0){
+      for(int i=0;i<links;i++){
+       vi[i].clear();
+       vc[i].clear();
+      }
+      vi=null;
+      vc=null;
+    }
+    if(dataoffsets!=null)dataoffsets=null;
+    if(pcmlengths!=null)pcmlengths=null;
+    if(serialnos!=null)serialnos=null;
+    if(offsets!=null)offsets=null;
+    oy.clear();
+    //if(datasource!=null)(vf->callbacks.close_func)(vf->datasource);
+    //memset(vf,0,sizeof(OggVorbis_File));
+    return(0);
+  }
+
+  static int fseek(InputStream fis,
+                         //int64_t off,
+                         long off,
+                         int whence){
+    if(fis instanceof SeekableInputStream){
+      SeekableInputStream sis=(SeekableInputStream)fis;
+      try{
+      if(whence==SEEK_SET){
+        sis.seek(off);
+      }
+      else if(whence==SEEK_END){
+        sis.seek(sis.getLength()-off);
+      }
+      else{
+       //System.out.println("seek: "+whence+" is not supported");
+      }
+      }
+      catch(Exception e){
+      }
+      return 0;
+    }
+    try{
+      if(whence==0){ fis.reset(); }
+      fis.skip(off);
+    }
+    catch(Exception e){return -1;}
+    return 0;
+  }
+
+  static long ftell(InputStream fis){
+    try{
+    if(fis instanceof SeekableInputStream){
+      SeekableInputStream sis=(SeekableInputStream)fis;
+      return (sis.tell());
+    }
+    }
+    catch(Exception e){
+    }
+    return 0;
+  }
+
+  // inspects the OggVorbis file and finds/documents all the logical
+  // bitstreams contained in it.  Tries to be tolerant of logical
+  // bitstream sections that are truncated/woogie. 
+  //
+  // return: -1) error
+  //          0) OK
+
+  int open(InputStream is, byte[] initial, int ibytes) throws JOrbisException {
+    //callbacks callbacks = {
+    // (size_t (*)(void *, size_t, size_t, void *))  fread,
+    // (int (*)(void *, int64_t, int))              _fseek,
+    // (int (*)(void *))                             fclose,
+    // (long (*)(void *))                            ftell
+    // };
+    return open_callbacks(is, initial, ibytes//, callbacks
+                            );
+  }
+
+  int open_callbacks(InputStream is, byte[] initial, 
+                       int ibytes//, callbacks callbacks
+                       ) throws JOrbisException {
+    int ret;
+    datasource=is;
+    //callbacks = _callbacks;
+    // init the framing state
+    oy.init();
+
+    // perhaps some data was previously read into a buffer for testing
+    // against other stream types.  Allow initialization from this
+    // previously read data (as we may be reading from a non-seekable
+    // stream)
+    if(initial!=null){
+      int index=oy.buffer(ibytes);
+      System.arraycopy(initial, 0, oy.data, index, ibytes);
+      oy.wrote(ibytes);
+    }
+    // can we seek? Stevens suggests the seek test was portable
+    if(is instanceof SeekableInputStream){ ret=open_seekable(); }
+    else{ ret=open_nonseekable(); }
+    if(ret!=0){
+      datasource=null;
+      clear();
+    }
+    return ret;
+  }
+
+  // How many logical bitstreams in this physical bitstream?
+  public int streams(){
+    return links;
+  }
+
+  // Is the FILE * associated with vf seekable?
+  public boolean seekable(){
+    return seekable;
+  }
+
+  // returns the bitrate for a given logical bitstream or the entire
+  // physical bitstream.  If the file is open for random access, it will
+  // find the *actual* average bitrate.  If the file is streaming, it
+  // returns the nominal bitrate (if set) else the average of the
+  // upper/lower bounds (if set) else -1 (unset).
+  // 
+  // If you want the actual bitrate field settings, get them from the
+  // vorbis_info structs
+
+  public int bitrate(int i){
+    if(i>=links)return(-1);
+    if(!seekable && i!=0)return(bitrate(0));
+    if(i<0){
+      long bits=0;
+      for(int j=0;j<links;j++){
+       bits+=(offsets[j+1]-dataoffsets[j])*8;
+      }
+      return((int)Math.rint(bits/time_total(-1)));
+    }
+    else{
+      if(seekable){
+       // return the actual bitrate
+       return((int)Math.rint((offsets[i+1]-dataoffsets[i])*8/time_total(i)));
+      }
+      else{
+       // return nominal if set
+       if(vi[i].bitrate_nominal>0){
+         return vi[i].bitrate_nominal;
+       }
+       else{
+         if(vi[i].bitrate_upper>0){
+           if(vi[i].bitrate_lower>0){
+             return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2;
+           }else{
+             return vi[i].bitrate_upper;
+           }
+         }
+         return(-1);
+       }
+      }
+    }
+  }
+
+  // returns the actual bitrate since last call.  returns -1 if no
+  // additional data to offer since last call (or at beginning of stream)
+  public int bitrate_instant(){
+    int _link=(seekable?current_link:0);
+    if(samptrack==0)return(-1);
+    int ret=(int)(bittrack/samptrack*vi[_link].rate+.5);
+    bittrack=0.f;
+    samptrack=0.f;
+    return(ret);
+  }
+
+  public int serialnumber(int i){
+    if(i>=links)return(-1);
+    if(!seekable && i>=0)return(serialnumber(-1));
+    if(i<0){
+      return(current_serialno);
+    }
+    else{
+      return(serialnos[i]);
+    }
+  }
+
+  // returns: total raw (compressed) length of content if i==-1
+  //          raw (compressed) length of that logical bitstream for i==0 to n
+  //          -1 if the stream is not seekable (we can't know the length)
+
+  public long raw_total(int i){
+    if(!seekable || i>=links)return(-1);
+    if(i<0){
+      long acc=0;               // bug?
+      for(int j=0;j<links;j++){
+       acc+=raw_total(j);
+      }
+      return(acc);
+    }
+    else{
+      return(offsets[i+1]-offsets[i]);
+    }
+  }
+
+  // returns: total PCM length (samples) of content if i==-1
+  //          PCM length (samples) of that logical bitstream for i==0 to n
+  //          -1 if the stream is not seekable (we can't know the length)
+  public long pcm_total(int i){
+    if(!seekable || i>=links)return(-1);
+    if(i<0){
+      long acc=0;
+      for(int j=0;j<links;j++){
+       acc+=pcm_total(j);
+      }
+      return(acc);
+    }
+    else{
+      return(pcmlengths[i]);
+    }
+  }
+
+  // returns: total seconds of content if i==-1
+  //          seconds in that logical bitstream for i==0 to n
+  //          -1 if the stream is not seekable (we can't know the length)
+  public float time_total(int i){
+    if(!seekable || i>=links)return(-1);
+    if(i<0){
+      float acc=0;
+      for(int j=0;j<links;j++){
+       acc+=time_total(j);
+      }
+      return(acc);
+    }
+    else{
+      return((float)(pcmlengths[i])/vi[i].rate);
+    }
+  }
+
+  // seek to an offset relative to the *compressed* data. This also
+  // immediately sucks in and decodes pages to update the PCM cursor. It
+  // will cross a logical bitstream boundary, but only if it can't get
+  // any packets out of the tail of the bitstream we seek to (so no
+  // surprises). 
+  // 
+  // returns zero on success, nonzero on failure
+
+  public int raw_seek(int pos){
+    if(!seekable)return(-1); // don't dump machine if we can't seek
+    if(pos<0 || pos>offsets[links]){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+
+    // clear out decoding machine state
+    pcm_offset=-1;
+    decode_clear();
+
+    // seek
+    seek_helper(pos);
+
+    // we need to make sure the pcm_offset is set.  We use the
+    // _fetch_packet helper to process one packet with readp set, then
+    // call it until it returns '0' with readp not set (the last packet
+    // from a page has the 'granulepos' field set, and that's how the
+    // helper updates the offset
+
+    switch(process_packet(1)){
+    case 0:
+      // oh, eof. There are no packets remaining.  Set the pcm offset to
+      // the end of file
+      pcm_offset=pcm_total(-1);
+      return(0);
+    case -1:
+      // error! missing data or invalid bitstream structure
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    default:
+      // all OK
+      break;
+    }
+    while(true){
+      switch(process_packet(0)){
+      case 0:
+       // the offset is set.  If it's a bogus bitstream with no offset
+       // information, it's not but that's not our fault.  We still run
+       // gracefully, we're just missing the offset
+       return(0);
+      case -1:
+       // error! missing data or invalid bitstream structure
+       //goto seek_error;
+       pcm_offset=-1;
+       decode_clear();
+       return -1;
+      default:
+       // continue processing packets
+       break;
+      }
+    }
+  
+  // seek_error:
+    // dump the machine so we're in a known state
+    //pcm_offset=-1;
+    //decode_clear();
+    //return -1;
+  }
+
+  // seek to a sample offset relative to the decompressed pcm stream 
+  // returns zero on success, nonzero on failure
+
+  public int pcm_seek(long pos){
+    int link=-1;
+    long total=pcm_total(-1);
+
+    if(!seekable)return(-1); // don't dump machine if we can't seek
+    if(pos<0 || pos>total){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+
+    // which bitstream section does this pcm offset occur in?
+    for(link=links-1;link>=0;link--){
+      total-=pcmlengths[link];
+      if(pos>=total)break;
+    }
+
+    // search within the logical bitstream for the page with the highest
+    // pcm_pos preceeding (or equal to) pos.  There is a danger here;
+    // missing pages or incorrect frame number information in the
+    // bitstream could make our task impossible.  Account for that (it
+    // would be an error condition)
+    {
+      long target=pos-total;
+      long end=offsets[link+1];
+      long begin=offsets[link];
+      int best=(int)begin;
+
+      Page og=new Page();
+      while(begin<end){
+       long bisect;
+       int ret;
+    
+       if(end-begin<CHUNKSIZE){
+         bisect=begin;
+       }
+       else{
+         bisect=(end+begin)/2;
+       }
+    
+       seek_helper(bisect);
+       ret=get_next_page(og,end-bisect);
+      
+       if(ret==-1){
+         end=bisect;
+       }
+       else{
+         long granulepos=og.granulepos();
+         if(granulepos<target){
+           best=ret;  // raw offset of packet with granulepos
+           begin=offset; // raw offset of next packet
+         }
+         else{
+           end=bisect;
+         }
+       }
+      }
+      // found our page. seek to it (call raw_seek).
+      if(raw_seek(best)!=0){
+       //goto seek_error;
+       pcm_offset=-1;
+       decode_clear();
+       return -1;
+      }
+    }
+
+    // verify result
+    if(pcm_offset>=pos){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+    if(pos>pcm_total(-1)){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+
+    // discard samples until we reach the desired position. Crossing a
+    // logical bitstream boundary with abandon is OK.
+    while(pcm_offset<pos){
+      float[][] pcm;
+      int target=(int)(pos-pcm_offset);
+      float[][][] _pcm=new float[1][][];
+      int[] _index=new int[getInfo(-1).channels];
+      int samples=vd.synthesis_pcmout(_pcm, _index);
+      pcm=_pcm[0];
+
+      if(samples>target)samples=target;
+      vd.synthesis_read(samples);
+      pcm_offset+=samples;
+    
+      if(samples<target)
+       if(process_packet(1)==0){
+         pcm_offset=pcm_total(-1); // eof
+       }
+    }
+    return 0;
+  
+  // seek_error:
+    // dump machine so we're in a known state
+    //pcm_offset=-1;
+    //decode_clear();
+    //return -1;
+  }
+
+  // seek to a playback time relative to the decompressed pcm stream 
+  // returns zero on success, nonzero on failure
+  int time_seek(float seconds){
+    // translate time to PCM position and call pcm_seek
+
+    int link=-1;
+    long pcm_total=pcm_total(-1);
+    float time_total=time_total(-1);
+
+    if(!seekable)return(-1); // don't dump machine if we can't seek
+    if(seconds<0 || seconds>time_total){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+  
+    // which bitstream section does this time offset occur in?
+    for(link=links-1;link>=0;link--){
+      pcm_total-=pcmlengths[link];
+      time_total-=time_total(link);
+      if(seconds>=time_total)break;
+    }
+
+    // enough information to convert time offset to pcm offset
+    {
+      long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate);
+      return(pcm_seek(target));
+    }
+
+  //seek_error:
+    // dump machine so we're in a known state
+    //pcm_offset=-1;
+    //decode_clear();
+    //return -1;
+  }
+
+  // tell the current stream offset cursor.  Note that seek followed by
+  // tell will likely not give the set offset due to caching
+  public long raw_tell(){
+    return(offset);
+  }
+
+  // return PCM offset (sample) of next PCM sample to be read
+  public long pcm_tell(){
+    return(pcm_offset);
+  }
+
+  // return time offset (seconds) of next PCM sample to be read
+  public float time_tell(){
+    // translate time to PCM position and call pcm_seek
+
+    int link=-1;
+    long pcm_total=0;
+    float time_total=0.f;
+  
+    if(seekable){
+      pcm_total=pcm_total(-1);
+      time_total=time_total(-1);
+  
+      // which bitstream section does this time offset occur in?
+      for(link=links-1;link>=0;link--){
+       pcm_total-=pcmlengths[link];
+       time_total-=time_total(link);
+       if(pcm_offset>=pcm_total)break;
+      }
+    }
+
+    return((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate);
+  }
+
+  //  link:   -1) return the vorbis_info struct for the bitstream section
+  //              currently being decoded
+  //         0-n) to request information for a specific bitstream section
+  //
+  // In the case of a non-seekable bitstream, any call returns the
+  // current bitstream.  NULL in the case that the machine is not
+  // initialized
+
+  public Info getInfo(int link){
+    if(seekable){
+      if(link<0){
+       if(decode_ready){
+         return vi[current_link];
+       }
+       else{
+         return null;
+       }
+      }
+      else{
+       if(link>=links){
+         return null;
+       }
+       else{
+         return vi[link];
+       }
+      }
+    }
+    else{
+      if(decode_ready){
+       return vi[0];
+      }
+      else{
+       return null;
+      }
+    }
+  }
+
+  public Comment getComment(int link){
+    if(seekable){
+      if(link<0){
+       if(decode_ready){ return vc[current_link]; }
+       else{ return null; }
+      }
+      else{
+       if(link>=links){ return null;}
+       else{ return vc[link]; }
+      }
+    }
+    else{
+      if(decode_ready){ return vc[0]; }
+      else{ return null; }
+    }
+  }
+
+  int host_is_big_endian() {
+    return 1;
+//    short pattern = 0xbabe;
+//    unsigned char *bytewise = (unsigned char *)&pattern;
+//    if (bytewise[0] == 0xba) return 1;
+//    assert(bytewise[0] == 0xbe);
+//    return 0;
+  }
+
+  // up to this point, everything could more or less hide the multiple
+  // logical bitstream nature of chaining from the toplevel application
+  // if the toplevel application didn't particularly care.  However, at
+  // the point that we actually read audio back, the multiple-section
+  // nature must surface: Multiple bitstream sections do not necessarily
+  // have to have the same number of channels or sampling rate.
+  // 
+  // read returns the sequential logical bitstream number currently
+  // being decoded along with the PCM data in order that the toplevel
+  // application can take action on channel/sample rate changes.  This
+  // number will be incremented even for streamed (non-seekable) streams
+  // (for seekable streams, it represents the actual logical bitstream
+  // index within the physical bitstream.  Note that the accessor
+  // functions above are aware of this dichotomy).
+  //
+  // input values: buffer) a buffer to hold packed PCM data for return
+  //               length) the byte length requested to be placed into buffer
+  //               bigendianp) should the data be packed LSB first (0) or
+  //                           MSB first (1)
+  //               word) word size for output.  currently 1 (byte) or 
+  //                     2 (16 bit short)
+  // 
+  // return values: -1) error/hole in data
+  //                 0) EOF
+  //                 n) number of bytes of PCM actually returned.  The
+  //                    below works on a packet-by-packet basis, so the
+  //                    return length is not related to the 'length' passed
+  //                    in, just guaranteed to fit.
+  // 
+  // *section) set to the logical bitstream number
+
+  int read(byte[] buffer,int length,
+          int bigendianp, int word, int sgned, int[] bitstream){
+    int host_endian = host_is_big_endian();
+    int index=0;
+
+    while(true){
+      if(decode_ready){
+       float[][] pcm;
+       float[][][] _pcm=new float[1][][];
+       int[] _index=new int[getInfo(-1).channels];
+       int samples=vd.synthesis_pcmout(_pcm, _index);
+       pcm=_pcm[0];
+       if(samples!=0){
+         // yay! proceed to pack data into the byte buffer
+         int channels=getInfo(-1).channels;
+         int bytespersample=word * channels;
+         if(samples>length/bytespersample)samples=length/bytespersample;
+       
+         // a tight loop to pack each size
+         {
+           int val;
+           if(word==1){
+             int off=(sgned!=0?0:128);
+             for(int j=0;j<samples;j++){
+               for(int i=0;i<channels;i++){
+                 val=(int)(pcm[i][_index[i]+j]*128. + 0.5);
+                 if(val>127)val=127;
+                 else if(val<-128)val=-128;
+                 buffer[index++]=(byte)(val+off);
+               }
+             }
+           }
+           else{
+             int off=(sgned!=0?0:32768);
+
+             if(host_endian==bigendianp){
+               if(sgned!=0){
+                 for(int i=0;i<channels;i++) { // It's faster in this order
+                   int src=_index[i];
+                   int dest=i;
+                   for(int j=0;j<samples;j++) {
+                     val=(int)(pcm[i][src+j]*32768. + 0.5);
+                     if(val>32767)val=32767;
+                     else if(val<-32768)val=-32768;
+                     buffer[dest]=(byte)(val>>>8);
+                     buffer[dest+1]=(byte)(val);
+                     dest+=channels*2;
+                   }
+                 }
+               }
+               else{
+                 for(int i=0;i<channels;i++) {
+                   float[] src=pcm[i];
+                   int dest=i;
+                   for(int j=0;j<samples;j++) {
+                     val=(int)(src[j]*32768. + 0.5);
+                     if(val>32767)val=32767;
+                     else if(val<-32768)val=-32768;
+                     buffer[dest]=(byte)((val+off)>>>8);
+                     buffer[dest+1]=(byte)(val+off);
+                     dest+=channels*2;
+                   }
+                 }
+               }
+             }
+             else if(bigendianp!=0){
+               for(int j=0;j<samples;j++){
+                 for(int i=0;i<channels;i++){
+                   val=(int)(pcm[i][j]*32768. + 0.5);
+                   if(val>32767)val=32767;
+                   else if(val<-32768)val=-32768;
+                   val+=off;
+                   buffer[index++]=(byte)(val>>>8);
+                   buffer[index++]=(byte)val;
+                 }
+               }
+             }
+             else{
+               //int val;
+               for(int j=0;j<samples;j++){
+                 for(int i=0;i<channels;i++){
+                   val=(int)(pcm[i][j]*32768. + 0.5);
+                   if(val>32767)val=32767;
+                   else if(val<-32768)val=-32768;
+                   val+=off;
+                   buffer[index++]=(byte)val;
+                   buffer[index++]=(byte)(val>>>8);
+                 }
+               }
+             }
+           }
+         }
+       
+         vd.synthesis_read(samples);
+         pcm_offset+=samples;
+         if(bitstream!=null)bitstream[0]=current_link;
+         return(samples*bytespersample);
+       }
+      }
+
+      // suck in another packet
+      switch(process_packet(1)){
+      case 0:
+       return(0);
+      case -1:
+       return -1;
+      default:
+       break;
+      }
+    }
+  }
+
+  public Info[] getInfo(){return vi;}
+  public Comment[] getComment(){return vc;}
+
+/*
+  public static void main(String[] arg){
+    try{
+      VorbisFile foo=new VorbisFile(arg[0]);
+      int links=foo.streams();
+      System.out.println("links="+links);
+      Comment[] comment=foo.getComment();
+      Info[] info=foo.getInfo();
+      for(int i=0; i<links; i++){
+        System.out.println(info[i]);
+        System.out.println(comment[i]);
+      }
+      System.out.println("raw_total: "+foo.raw_total(-1));
+      System.out.println("pcm_total: "+foo.pcm_total(-1));
+      System.out.println("time_total: "+foo.time_total(-1));
+    }
+    catch(Exception e){
+      System.err.println(e);
+    }
+  }
+*/
+
+  public void close() throws java.io.IOException {
+    datasource.close();
+  }
+
+  class SeekableInputStream extends InputStream {
+    java.io.RandomAccessFile raf=null;
+    final String mode="r";
+    private SeekableInputStream(){
+    }
+    SeekableInputStream(String file) throws java.io.IOException{
+      raf=new java.io.RandomAccessFile(file, mode);
+    }
+    public int read() throws java.io.IOException{
+      return raf.read();
+    }
+    public int read(byte[] buf) throws java.io.IOException{
+      return raf.read(buf);
+    }
+    public int read(byte[] buf , int s, int len) throws java.io.IOException{
+      return raf.read(buf, s, len);
+    }
+    public long skip(long n) throws java.io.IOException{
+      return (long)(raf.skipBytes((int)n));
+    }
+    public long getLength() throws java.io.IOException{
+      return raf.length();
+    }
+    public long tell() throws java.io.IOException{
+      return raf.getFilePointer();
+    }
+    public int available() throws java.io.IOException{
+      return (raf.length()==raf.getFilePointer())? 0 : 1;
+    }
+    public void close() throws java.io.IOException{
+      raf.close();
+    }
+    public synchronized void mark(int m){
+    }
+    public synchronized void reset() throws java.io.IOException{
+    }
+    public boolean markSupported(){
+      return false;
+    }
+    public void seek(long pos) throws java.io.IOException{
+      raf.seek(pos);
+    }
+  }
+
+}
diff --git a/jorbis/src/com/jcraft/jorbis/VorbisFile.java.new b/jorbis/src/com/jcraft/jorbis/VorbisFile.java.new
new file mode 100644 (file)
index 0000000..1f822b0
--- /dev/null
@@ -0,0 +1,1240 @@
+/* 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.*;
+import java.io.InputStream;
+
+public class VorbisFile{
+  static final int CHUNKSIZE=4096;
+  static final int SEEK_SET=0;
+
+  InputStream datasource;
+  boolean seekable=false;
+  long offset;
+  long end;
+  
+  SyncState oy=new SyncState();
+
+  int links;
+  Comment[] vc;
+  Info[] vi;
+
+  long[] offsets;
+  long[] dataoffsets;
+  int[] serialnos;
+  long[] pcmlengths;
+
+
+
+  // Decoding working state local storage
+  long pcm_offset;
+  boolean decode_ready=false;
+  int current_serialno;
+  int current_link;
+
+  float bittrack;
+  float samptrack;
+
+  StreamState os=new StreamState(); // take physical pages, weld into a logical
+                                    // stream of packets
+  DspState vd=new DspState(); // central working state for 
+                              // the packet->PCM decoder
+  Block vb=new Block(vd);     // local working space for packet->PCM decode
+
+  //ov_callbacks callbacks;
+
+  public VorbisFile(String file) throws JOrbisException {
+    super();
+    InputStream is=null;
+    try{ is=new java.io.BufferedInputStream(new java.io.FileInputStream(file));}
+    catch(Exception e){
+      throw new JOrbisException("VorbisFile: "+e.toString());
+    }
+    int ret=open(is, null, 0);
+    if(ret==-1){
+      throw new JOrbisException("VorbisFile: open return -1");
+    }
+  }
+
+  public VorbisFile(InputStream is, byte[] initial, int ibytes)
+    throws JOrbisException {
+    super();
+    int ret=open(is, initial, ibytes);
+    if(ret==-1){
+    }
+  }
+
+  private int get_data(){
+    int index=oy.buffer(CHUNKSIZE);
+    byte[] buffer=oy.data;
+//  int bytes=callbacks.read_func(buffer, index, 1, CHUNKSIZE, datasource);
+    int bytes=0;
+    try{
+      bytes=datasource.read(buffer, index, CHUNKSIZE);
+    }
+    catch(Exception e){System.err.println(e);}
+    oy.wrote(bytes);
+    return bytes;
+  }
+
+  private void seek_helper(int offst){
+    //callbacks.seek_func(datasource, offst, SEEK_SET);
+    fseek64_wrap(datasource, offst, SEEK_SET);
+    this.offset=offst;
+    oy.reset();
+  }
+
+  private int get_next_page(Page page, int boundary){
+    if(boundary>0) boundary+=offset;
+    while(true){
+      int more;
+      if(boundary>0 && offset>=boundary)return -1;
+      more=oy.pageseek(page);
+      if(more<0){offset-=more;}
+      else{
+       if(more==0){
+         if(boundary==0)return -1;
+         if(get_data()<=0)return -1;
+       }
+       else{
+         int ret=(int)offset; //!!!
+         offset+=more;
+         return ret;
+       }
+      }
+    }
+  }
+
+  private int get_prev_page(Page page){
+    int begin=(int)offset; //!!!
+    int ret;
+    int offst=-1;
+    while(offst==-1){
+      begin-=CHUNKSIZE;
+      seek_helper(begin);
+      while(offset<begin+CHUNKSIZE){
+       ret=get_next_page(page, begin+CHUNKSIZE-((int)offset));
+       if(ret==-1){ break; }
+       else{ offst=ret; }
+      }
+    }
+    seek_helper((int)offset); //!!!
+    ret=get_next_page(page, CHUNKSIZE);
+    if(ret==-1){
+      System.err.println("Missed page fencepost at end of logical bitstream Exiting");
+      System.exit(1);
+    }
+    return offst;
+  }
+
+  void bisect_forward_serialno(int begin, int searched, int end, int currentno, int m){
+    int endsearched=end;
+    int next=end;
+    Page page=new Page();
+    int ret;
+    while(searched<endsearched){
+      int bisect;
+      if(endsearched-searched<CHUNKSIZE){
+       bisect=searched;
+      }
+      else{
+       bisect=(searched+endsearched)/2;
+      }
+
+      seek_helper(bisect);
+      ret=get_next_page(page, -1);
+      if(ret<0 || page.serialno()!=currentno){
+       endsearched=bisect;
+       if(ret>=0)next=ret;
+      }
+      else{
+       searched=ret+page.header_len+page.body_len;
+      }
+    }
+    seek_helper(next);
+    ret=get_next_page(page, -1);
+
+    if(searched>=end || ret==-1){
+      links=m+1;
+      offsets=new long[m+2];
+      offsets[m+1]=searched;
+    }
+    else{
+      bisect_forward_serialno(next, (int)offset, end, page.serialno(), m+1);
+    }
+    offsets[m]=begin;
+  }
+
+  // uses the local ogg_stream storage in vf; this is important for
+  // non-streaming input sources
+  int fetch_headers(Info vi, Comment vc, int[] serialno){
+    //System.err.println("fetch_headers");
+    Page og=new Page();
+    Packet op=new Packet();
+    int ret;
+
+    ret=get_next_page(og, CHUNKSIZE);
+    if(ret==-1){
+      System.err.println("Did not find initial header for bitstream.");
+      return -1;
+    }
+  
+    if(serialno!=null)serialno[0]=og.serialno();
+
+    os.init(og.serialno());
+  
+    // extract the initial header from the first page and verify that the
+    // Ogg bitstream is in fact Vorbis data
+  
+    vi.init();
+    vc.init();
+  
+    int i=0;
+    while(i<3){
+      os.pagein(og);
+      while(i<3){
+       int result=os.packetout(op);
+       if(result==0)break;
+       if(result==-1){
+         System.err.println("Corrupt header in logical bitstream.");
+         //goto bail_header;
+         vi.clear();
+         vc.clear();
+         os.clear();
+          return -1;
+       }
+       if(vi.synthesis_headerin(vc, op)!=0){
+         System.err.println("Illegal header in logical bitstream.");
+         //goto bail_header;
+         vi.clear();
+         vc.clear();
+         os.clear();
+         return -1;
+       }
+       i++;
+      }
+      if(i<3)
+       if(get_next_page(og, 1)<0){
+         System.err.println("Missing header in logical bitstream.");
+         //goto bail_header;
+         vi.clear();
+         vc.clear();
+         os.clear();
+         return -1;
+       }
+    }
+    return 0; 
+
+//  bail_header:
+//    vorbis_info_clear(vi);
+//    vorbis_comment_clear(vc);
+//    ogg_stream_clear(&vf->os);
+//    return -1;
+  }
+
+  // last step of the OggVorbis_File initialization; get all the
+  // vorbis_info structs and PCM positions.  Only called by the seekable
+  // initialization (local stream storage is hacked slightly; pay
+  // attention to how that's done)
+  void prefetch_all_headers(Info first_i,Comment first_c, int dataoffset){
+    Page og=new Page();
+    int ret;
+  
+    vi=new Info[links];
+    vc=new Comment[links];
+    dataoffsets=new long[links];
+    pcmlengths=new long[links];
+    serialnos=new int[links];
+  
+    for(int i=0;i<links;i++){
+      if(first_i!=null && first_c!=null && i==0){
+       // we already grabbed the initial header earlier.  This just
+       // saves the waste of grabbing it again
+       // !!!!!!!!!!!!!
+       vi[i]=first_i;
+       //memcpy(vf->vi+i,first_i,sizeof(vorbis_info));
+       vc[i]=first_c;
+       //memcpy(vf->vc+i,first_c,sizeof(vorbis_comment));
+       dataoffsets[i]=dataoffset;
+      }
+      else{
+       // seek to the location of the initial header
+       seek_helper((int)offsets[i]); //!!!
+       if(fetch_headers(vi[i], vc[i], null)==-1){
+         System.err.println("Error opening logical bitstream #"+(i+1)+"\n");
+         dataoffsets[i]=-1;
+       }
+       else{
+         dataoffsets[i]=offset;
+         os.clear();
+       }
+      }
+
+      // get the serial number and PCM length of this link. To do this,
+      // get the last page of the stream
+      {
+       int end=(int)offsets[i+1]; //!!!
+       seek_helper(end);
+
+       while(true){
+         ret=get_prev_page(og);
+         if(ret==-1){
+           // this should not be possible
+           System.err.println("Could not find last page of logical "+
+                              "bitstream #"+(i)+"\n");
+           vi[i].clear();
+           vc[i].clear();
+           break;
+         }
+         if(og.granulepos()!=-1){
+           serialnos[i]=og.serialno();
+           pcmlengths[i]=og.granulepos();
+           break;
+         }
+       }
+      }
+    }
+  }
+
+  int make_decode_ready(){
+    if(decode_ready)System.exit(1);
+    vd.synthesis_init(vi[0]);
+    vb.init(vd);
+    decode_ready=true;
+    return(0);
+  }
+
+  int open_seekable(){
+    Info initial_i=new Info();
+    Comment initial_c=new Comment();
+    int serialno,end;
+    int ret;
+    int dataoffset;
+    Page og=new Page();
+System.out.println("open_seekable");  
+    // is this even vorbis...?
+    int[] foo=new int[1];
+    ret=fetch_headers(initial_i, initial_c, foo);
+    serialno=foo[0];
+    dataoffset=(int)offset; //!!
+    os.clear();
+    if(ret==-1)return(-1);
+  
+    // we can seek, so set out learning all about this file
+    seekable=true;
+    //(callbacks.seek_func)(datasource, 0, SEEK_END);
+    fseek64_wrap(datasource, (int)offset, SEEK_SET);
+    //offset=end=(callbacks.tell_func)(datasource);
+    end=(int)offset;
+  
+    // We get the offset for the last page of the physical bitstream.
+    // Most OggVorbis files will contain a single logical bitstream
+    end=get_prev_page(og);
+
+    // moer than one logical bitstream?
+    if(og.serialno()!=serialno){
+      // Chained bitstream. Bisect-search each logical bitstream
+      // section.  Do so based on serial number only
+      bisect_forward_serialno(0,0,end+1,serialno,0);
+    }
+    else{
+      // Only one logical bitstream
+      bisect_forward_serialno(0,end,end+1,serialno,0);
+    }
+    prefetch_all_headers(initial_i, initial_c, dataoffset);
+
+System.out.println("?");  
+    return(raw_seek(0));
+  }
+
+  int open_nonseekable(){
+    //System.err.println("open_nonseekable");
+    // we cannot seek. Set up a 'single' (current) logical bitstream entry
+    links=1;
+    vi=new Info[links]; vi[0]=new Info(); // ??
+    vc=new Comment[links]; vc[0]=new Comment(); // ?? bug?
+
+    // Try to fetch the headers, maintaining all the storage
+    int[]foo=new int[1];
+    if(fetch_headers(vi[0], vc[0], foo)==-1)return(-1);
+    current_serialno=foo[0];
+    make_decode_ready();
+    return 0;
+  }
+
+  // clear out the current logical bitstream decoder
+  void decode_clear(){
+    os.clear();
+    vd.clear();
+    vb.clear();
+    decode_ready=false;
+    bittrack=0.f;
+    samptrack=0.f;
+  }
+
+  // fetch and process a packet.  Handles the case where we're at a
+  // bitstream boundary and dumps the decoding machine.  If the decoding
+  // machine is unloaded, it loads it.  It also keeps pcm_offset up to
+  // date (seek and read both use this.  seek uses a special hack with
+  // readp). 
+  //
+  // return: -1) hole in the data (lost packet) 
+  //          0) need more date (only if readp==0)/eof
+  //          1) got a packet 
+
+  int process_packet(int readp){
+System.out.println("porcess_packet:"+ readp+" , decode_ready="+decode_ready);
+    Page og=new Page();
+
+    // handle one packet.  Try to fetch it from current stream state
+    // extract packets from page
+    while(true){
+      // process a packet if we can.  If the machine isn't loaded,
+      // neither is a page
+      if(decode_ready){
+       Packet op=new Packet();
+       int result=os.packetout(op);
+       long granulepos;
+        // if(result==-1)return(-1); // hole in the data. For now, swallow
+                                    // and go. We'll need to add a real
+                                    // error code in a bit.
+       if(result>0){
+         // got a packet.  process it
+         granulepos=op.granulepos;
+         if(vb.synthesis(op)==0){ // lazy check for lazy
+                                   // header handling.  The
+                                  // header packets aren't
+                                  // audio, so if/when we
+                                  // submit them,
+                                  // vorbis_synthesis will
+                                   // reject them
+           // suck in the synthesis data and track bitrate
+           {
+             int oldsamples=vd.synthesis_pcmout(null, null);
+             vd.synthesis_blockin(vb);
+             samptrack+=vd.synthesis_pcmout(null, null)-oldsamples;
+             bittrack+=op.bytes*8;
+           }
+         
+           // update the pcm offset.
+           if(granulepos!=-1 && op.e_o_s==0){
+             int link=(seekable?current_link:0);
+             int samples;
+             // this packet has a pcm_offset on it (the last packet
+             // completed on a page carries the offset) After processing
+             // (above), we know the pcm position of the *last* sample
+             // ready to be returned. Find the offset of the *first*
+             // 
+             // As an aside, this trick is inaccurate if we begin
+             // reading anew right at the last page; the end-of-stream
+             // granulepos declares the last frame in the stream, and the
+             // last packet of the last page may be a partial frame.
+             // So, we need a previous granulepos from an in-sequence page
+             // to have a reference point.  Thus the !op.e_o_s clause above
+           
+             samples=vd.synthesis_pcmout(null, null);
+             granulepos-=samples;
+             for(int i=0;i<link;i++){
+               granulepos+=pcmlengths[i];
+             }
+             pcm_offset=granulepos;
+           }
+           return(1);
+         }
+       }
+      }
+
+      if(readp==0)return(0);
+      if(get_next_page(og,-1)<0)return(0); // eof. leave unitialized
+
+      // bitrate tracking; add the header's bytes here, the body bytes
+      // are done by packet above
+      bittrack+=og.header_len*8;
+
+      // has our decoding just traversed a bitstream boundary?
+      if(decode_ready){
+       if(current_serialno!=og.serialno()){
+         decode_clear();
+       }
+      }
+
+      // Do we need to load a new machine before submitting the page?
+      // This is different in the seekable and non-seekable cases.  
+      // 
+      // In the seekable case, we already have all the header
+      // information loaded and cached; we just initialize the machine
+      // with it and continue on our merry way.
+      // 
+      // In the non-seekable (streaming) case, we'll only be at a
+      // boundary if we just left the previous logical bitstream and
+      // we're now nominally at the header of the next bitstream
+
+      if(!decode_ready){
+       int i;
+       if(seekable){
+         current_serialno=og.serialno();
+       
+         // match the serialno to bitstream section.  We use this rather than
+         // offset positions to avoid problems near logical bitstream
+         // boundaries
+         for(i=0;i<links;i++){
+           if(serialnos[i]==current_serialno)break;
+         }
+         if(i==links)return(-1); // sign of a bogus stream.  error out,
+                                 // leave machine uninitialized
+         current_link=i;
+
+         os.init(current_serialno);
+         os.reset(); 
+
+       }
+       else{
+         // we're streaming
+         // fetch the three header packets, build the info struct
+         int foo[]=new int[1];
+         fetch_headers(vi[0], vc[0], foo);
+         current_serialno=foo[0];
+         current_link++;
+         i=0;
+       }
+       make_decode_ready();
+      }
+      os.pagein(og);
+    }
+  }
+
+  //The helpers are over; it's all toplevel interface from here on out
+  // clear out the OggVorbis_File struct
+  int clear(){
+    vb.clear();
+    vd.clear();
+    os.clear();
+    
+    if(vi!=null && links!=0){
+      for(int i=0;i<links;i++){
+       vi[i].clear();
+       vc[i].clear();
+      }
+      vi=null;
+      vc=null;
+    }
+    if(dataoffsets!=null)dataoffsets=null;
+    if(pcmlengths!=null)pcmlengths=null;
+    if(serialnos!=null)serialnos=null;
+    if(offsets!=null)offsets=null;
+    oy.clear();
+    //if(datasource!=null)(vf->callbacks.close_func)(vf->datasource);
+    //memset(vf,0,sizeof(OggVorbis_File));
+    return(0);
+  }
+
+  static int fseek64_wrap(InputStream fis,
+                         //int64_t off,
+                         int off,
+                         int whence){
+
+    if(!fis.markSupported()){ return -1; }
+    try{
+      try{if(whence==0){ fis.reset(); }}
+      catch(Exception ee){System.out.println(ee);}
+      fis.skip(off);
+    }
+    catch(Exception e){        System.out.println(e);
+    //return -1;
+    }
+    return 0;
+  }
+
+  // inspects the OggVorbis file and finds/documents all the logical
+  // bitstreams contained in it.  Tries to be tolerant of logical
+  // bitstream sections that are truncated/woogie. 
+  //
+  // return: -1) error
+  //          0) OK
+
+  int open(InputStream is, byte[] initial, int ibytes){
+    return open_callbacks(is, initial, ibytes//, callbacks
+                            );
+  }
+
+  int open_callbacks(InputStream is, byte[] initial, 
+                       int ibytes//, callbacks callbacks
+                       ){
+//  int offset=callbacks.seek_func(f,0,SEEK_CUR);
+    int _offset=fseek64_wrap(is, (int)offset, SEEK_SET);
+    int ret;
+    // memset(vf,0,sizeof(OggVorbis_File));
+    datasource=is;
+    //callbacks = _callbacks;
+
+    // init the framing state
+    oy.init();
+
+    // perhaps some data was previously read into a buffer for testing
+    // against other stream types.  Allow initialization from this
+    // previously read data (as we may be reading from a non-seekable
+    // stream)
+    if(initial!=null){
+      int index=oy.buffer(ibytes);
+      System.arraycopy(initial, 0, oy.data, index, ibytes);
+      oy.wrote(ibytes);
+    }
+
+System.out.println("open_callbacks="+_offset);
+    // can we seek? Stevens suggests the seek test was portable
+    if(_offset!=-1){ ret=open_seekable(); }
+    else{ ret=open_nonseekable(); }
+
+System.out.println("ret="+ret);
+
+    if(ret!=0){
+      datasource=null;
+      clear();
+    }
+
+    return(ret);
+  }
+
+  // How many logical bitstreams in this physical bitstream?
+  public int streams(){
+    return links;
+  }
+
+  // Is the FILE * associated with vf seekable?
+  public boolean seekable(){
+    return seekable;
+  }
+
+  // returns the bitrate for a given logical bitstream or the entire
+  // physical bitstream.  If the file is open for random access, it will
+  // find the *actual* average bitrate.  If the file is streaming, it
+  // returns the nominal bitrate (if set) else the average of the
+  // upper/lower bounds (if set) else -1 (unset).
+  // 
+  // If you want the actual bitrate field settings, get them from the
+  // vorbis_info structs
+
+  public int bitrate(int i){
+    if(i>=links)return(-1);
+    if(!seekable && i!=0)return(bitrate(0));
+    if(i<0){
+      long bits=0;
+      for(int j=0;j<links;j++){
+       bits+=(offsets[j+1]-dataoffsets[j])*8;
+      }
+      return((int)Math.rint(bits/time_total(-1)));
+    }
+    else{
+      if(seekable){
+       // return the actual bitrate
+       return((int)Math.rint((offsets[i+1]-dataoffsets[i])*8/time_total(i)));
+      }
+      else{
+       // return nominal if set
+       if(vi[i].bitrate_nominal>0){
+         return vi[i].bitrate_nominal;
+       }
+       else{
+         if(vi[i].bitrate_upper>0){
+           if(vi[i].bitrate_lower>0){
+             return (vi[i].bitrate_upper+vi[i].bitrate_lower)/2;
+           }else{
+             return vi[i].bitrate_upper;
+           }
+         }
+         return(-1);
+       }
+      }
+    }
+  }
+
+  // returns the actual bitrate since last call.  returns -1 if no
+  // additional data to offer since last call (or at beginning of stream)
+  public int bitrate_instant(){
+    int _link=(seekable?current_link:0);
+    if(samptrack==0)return(-1);
+    int ret=(int)(bittrack/samptrack*vi[_link].rate+.5);
+    bittrack=0.f;
+    samptrack=0.f;
+    return(ret);
+  }
+
+  public int serialnumber(int i){
+    if(i>=links)return(-1);
+    if(!seekable && i>=0)return(serialnumber(-1));
+    if(i<0){
+      return(current_serialno);
+    }
+    else{
+      return(serialnos[i]);
+    }
+  }
+
+  // returns: total raw (compressed) length of content if i==-1
+  //          raw (compressed) length of that logical bitstream for i==0 to n
+  //          -1 if the stream is not seekable (we can't know the length)
+
+  public long raw_total(int i){
+System.out.println("raw_total: "+seekable);
+    if(!seekable || i>=links)return(-1);
+    if(i<0){
+      long acc=0;               // bug?
+      for(int j=0;j<links;j++){
+       acc+=raw_total(j);
+      }
+      return(acc);
+    }
+    else{
+      return(offsets[i+1]-offsets[i]);
+    }
+  }
+
+  // returns: total PCM length (samples) of content if i==-1
+  //          PCM length (samples) of that logical bitstream for i==0 to n
+  //          -1 if the stream is not seekable (we can't know the length)
+  public long pcm_total(int i){
+    if(!seekable || i>=links)return(-1);
+    if(i<0){
+      long acc=0;
+      for(int j=0;j<links;j++){
+       acc+=pcm_total(j);
+      }
+      return(acc);
+    }
+    else{
+      return(pcmlengths[i]);
+    }
+  }
+
+  // returns: total seconds of content if i==-1
+  //          seconds in that logical bitstream for i==0 to n
+  //          -1 if the stream is not seekable (we can't know the length)
+  public float time_total(int i){
+    if(!seekable || i>=links)return(-1);
+    if(i<0){
+      float acc=0;
+      for(int j=0;j<links;j++){
+       acc+=time_total(j);
+      }
+      return(acc);
+    }
+    else{
+      return((float)(pcmlengths[i])/vi[i].rate);
+    }
+  }
+
+  // seek to an offset relative to the *compressed* data. This also
+  // immediately sucks in and decodes pages to update the PCM cursor. It
+  // will cross a logical bitstream boundary, but only if it can't get
+  // any packets out of the tail of the bitstream we seek to (so no
+  // surprises). 
+  // 
+  // returns zero on success, nonzero on failure
+
+  public int raw_seek(int pos){
+System.out.println("raw_seek: "+pos);
+    if(!seekable)return(-1); // don't dump machine if we can't seek
+    if(pos<0 || pos>offsets[links]){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+System.out.println("#1");
+    // clear out decoding machine state
+    pcm_offset=-1;
+System.out.println("#2");
+    decode_clear();
+System.out.println("#3");
+    // seek
+    seek_helper(pos);
+
+    // we need to make sure the pcm_offset is set.  We use the
+    // _fetch_packet helper to process one packet with readp set, then
+    // call it until it returns '0' with readp not set (the last packet
+    // from a page has the 'granulepos' field set, and that's how the
+    // helper updates the offset
+System.out.println("#4");
+    switch(process_packet(1)){
+    case 0:
+System.out.println("?0");
+      // oh, eof. There are no packets remaining.  Set the pcm offset to
+      // the end of file
+      pcm_offset=pcm_total(-1);
+      return(0);
+    case -1:
+System.out.println("?-1");
+      // error! missing data or invalid bitstream structure
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    default:
+System.out.println("?break");
+      // all OK
+      break;
+    }
+System.out.println("pcm_offset="+pcm_offset);
+    while(true){
+      switch(process_packet(0)){
+      case 0:
+       // the offset is set.  If it's a bogus bitstream with no offset
+       // information, it's not but that's not our fault.  We still run
+       // gracefully, we're just missing the offset
+       return(0);
+      case -1:
+       // error! missing data or invalid bitstream structure
+       //goto seek_error;
+       pcm_offset=-1;
+       decode_clear();
+       return -1;
+      default:
+       // continue processing packets
+       break;
+      }
+    }
+  
+  // seek_error:
+    // dump the machine so we're in a known state
+    //pcm_offset=-1;
+    //decode_clear();
+    //return -1;
+  }
+
+  // seek to a sample offset relative to the decompressed pcm stream 
+  // returns zero on success, nonzero on failure
+
+  public int pcm_seek(long pos){
+    int link=-1;
+    long total=pcm_total(-1);
+
+    if(!seekable)return(-1); // don't dump machine if we can't seek
+    if(pos<0 || pos>total){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+
+    // which bitstream section does this pcm offset occur in?
+    for(link=links-1;link>=0;link--){
+      total-=pcmlengths[link];
+      if(pos>=total)break;
+    }
+
+    // search within the logical bitstream for the page with the highest
+    // pcm_pos preceeding (or equal to) pos.  There is a danger here;
+    // missing pages or incorrect frame number information in the
+    // bitstream could make our task impossible.  Account for that (it
+    // would be an error condition)
+    {
+      long target=pos-total;
+      int end=(int)offsets[link+1];
+      int begin=(int)offsets[link];
+      int best=begin;
+
+      Page og=new Page();
+      while(begin<end){
+       int bisect;
+       int ret;
+    
+       if(end-begin<CHUNKSIZE){
+         bisect=begin;
+       }
+       else{
+         bisect=(end+begin)/2;
+       }
+    
+       seek_helper(bisect);
+       ret=get_next_page(og,end-bisect);
+      
+       if(ret==-1){
+         end=bisect;
+       }
+       else{
+         long granulepos=og.granulepos();
+         if(granulepos<target){
+           best=ret;  // raw offset of packet with granulepos
+           begin=(int)offset; // raw offset of next packet
+         }
+         else{
+           end=bisect;
+         }
+       }
+      }
+      // found our page. seek to it (call raw_seek).
+      if(raw_seek(best)!=0){
+       //goto seek_error;
+       pcm_offset=-1;
+       decode_clear();
+       return -1;
+      }
+    }
+
+    // verify result
+    if(pcm_offset>=pos){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+    if(pos>pcm_total(-1)){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+
+    // discard samples until we reach the desired position. Crossing a
+    // logical bitstream boundary with abandon is OK.
+    while(pcm_offset<pos){
+      float[][] pcm;
+      int target=(int)(pos-pcm_offset);
+      float[][][] _pcm=new float[1][][];
+      int[] _index=new int[info(-1).channels];
+      int samples=vd.synthesis_pcmout(_pcm, _index);
+      pcm=_pcm[0];
+
+      if(samples>target)samples=target;
+      vd.synthesis_read(samples);
+      pcm_offset+=samples;
+    
+      if(samples<target)
+       if(process_packet(1)==0){
+         pcm_offset=pcm_total(-1); // eof
+       }
+    }
+    return 0;
+  
+  // seek_error:
+    // dump machine so we're in a known state
+    //pcm_offset=-1;
+    //decode_clear();
+    //return -1;
+  }
+
+  // seek to a playback time relative to the decompressed pcm stream 
+  // returns zero on success, nonzero on failure
+  public int time_seek(float seconds){
+    // translate time to PCM position and call pcm_seek
+
+    int link=-1;
+    long pcm_total=pcm_total(-1);
+    float time_total=time_total(-1);
+
+    if(!seekable)return(-1); // don't dump machine if we can't seek
+    if(seconds<0 || seconds>time_total){
+      //goto seek_error;
+      pcm_offset=-1;
+      decode_clear();
+      return -1;
+    }
+  
+    // which bitstream section does this time offset occur in?
+    for(link=links-1;link>=0;link--){
+      pcm_total-=pcmlengths[link];
+      time_total-=time_total(link);
+      if(seconds>=time_total)break;
+    }
+
+    // enough information to convert time offset to pcm offset
+    {
+      long target=(long)(pcm_total+(seconds-time_total)*vi[link].rate);
+      return(pcm_seek(target));
+    }
+
+  //seek_error:
+    // dump machine so we're in a known state
+    //pcm_offset=-1;
+    //decode_clear();
+    //return -1;
+  }
+
+  // tell the current stream offset cursor.  Note that seek followed by
+  // tell will likely not give the set offset due to caching
+  public long raw_tell(){
+    return(offset);
+  }
+
+  // return PCM offset (sample) of next PCM sample to be read
+  public long pcm_tell(){
+    return(pcm_offset);
+  }
+
+  // return time offset (seconds) of next PCM sample to be read
+  public float time_tell(){
+    // translate time to PCM position and call pcm_seek
+
+    int link=-1;
+    long pcm_total=0;
+    float time_total=0.f;
+  
+    if(seekable){
+      pcm_total=pcm_total(-1);
+      time_total=time_total(-1);
+  
+      // which bitstream section does this time offset occur in?
+      for(link=links-1;link>=0;link--){
+       pcm_total-=pcmlengths[link];
+       time_total-=time_total(link);
+       if(pcm_offset>=pcm_total)break;
+      }
+    }
+
+    return((float)time_total+(float)(pcm_offset-pcm_total)/vi[link].rate);
+  }
+
+  //  link:   -1) return the vorbis_info struct for the bitstream section
+  //              currently being decoded
+  //         0-n) to request information for a specific bitstream section
+  //
+  // In the case of a non-seekable bitstream, any call returns the
+  // current bitstream.  NULL in the case that the machine is not
+  // initialized
+
+  public Info info(int link){
+    if(seekable){
+      if(link<0){
+       if(decode_ready){
+         return vi[current_link];
+       }
+       else{
+         return null;
+       }
+      }
+      else{
+       if(link>=links){
+         return null;
+       }
+       else{
+         return vi[link];
+       }
+      }
+    }
+    else{
+      if(decode_ready){
+       return vi[0];
+      }
+      else{
+       return null;
+      }
+    }
+  }
+
+  public Comment comment(int link){
+    if(seekable){
+      if(link<0){
+       if(decode_ready){ return vc[current_link]; }
+       else{ return null; }
+      }
+      else{
+       if(link>=links){ return null;}
+       else{ return vc[link]; }
+      }
+    }
+    else{
+      if(decode_ready){ return vc[0]; }
+      else{ return null; }
+    }
+  }
+
+  int host_is_big_endian() {
+    return 1;
+//    short pattern = 0xbabe;
+//    unsigned char *bytewise = (unsigned char *)&pattern;
+//    if (bytewise[0] == 0xba) return 1;
+//    assert(bytewise[0] == 0xbe);
+//    return 0;
+  }
+
+  // up to this point, everything could more or less hide the multiple
+  // logical bitstream nature of chaining from the toplevel application
+  // if the toplevel application didn't particularly care.  However, at
+  // the point that we actually read audio back, the multiple-section
+  // nature must surface: Multiple bitstream sections do not necessarily
+  // have to have the same number of channels or sampling rate.
+  // 
+  // read returns the sequential logical bitstream number currently
+  // being decoded along with the PCM data in order that the toplevel
+  // application can take action on channel/sample rate changes.  This
+  // number will be incremented even for streamed (non-seekable) streams
+  // (for seekable streams, it represents the actual logical bitstream
+  // index within the physical bitstream.  Note that the accessor
+  // functions above are aware of this dichotomy).
+  //
+  // input values: buffer) a buffer to hold packed PCM data for return
+  //               length) the byte length requested to be placed into buffer
+  //               bigendianp) should the data be packed LSB first (0) or
+  //                           MSB first (1)
+  //               word) word size for output.  currently 1 (byte) or 
+  //                     2 (16 bit short)
+  // 
+  // return values: -1) error/hole in data
+  //                 0) EOF
+  //                 n) number of bytes of PCM actually returned.  The
+  //                    below works on a packet-by-packet basis, so the
+  //                    return length is not related to the 'length' passed
+  //                    in, just guaranteed to fit.
+  // 
+  // *section) set to the logical bitstream number
+
+  int read(byte[] buffer,int length,
+          int bigendianp, int word, int sgned, int[] bitstream){
+    int host_endian = host_is_big_endian();
+    int index=0;
+
+    while(true){
+      if(decode_ready){
+       float[][] pcm;
+       float[][][] _pcm=new float[1][][];
+       int[] _index=new int[info(-1).channels];
+       int samples=vd.synthesis_pcmout(_pcm, _index);
+       pcm=_pcm[0];
+       if(samples!=0){
+         // yay! proceed to pack data into the byte buffer
+         int channels=info(-1).channels;
+         int bytespersample=word * channels;
+         if(samples>length/bytespersample)samples=length/bytespersample;
+       
+         // a tight loop to pack each size
+         {
+           int val;
+           if(word==1){
+             int off=(sgned!=0?0:128);
+             for(int j=0;j<samples;j++){
+               for(int i=0;i<channels;i++){
+                 val=(int)(pcm[i][_index[i]+j]*128. + 0.5);
+                 if(val>127)val=127;
+                 else if(val<-128)val=-128;
+                 buffer[index++]=(byte)(val+off);
+               }
+             }
+           }
+           else{
+             int off=(sgned!=0?0:32768);
+
+             if(host_endian==bigendianp){
+               if(sgned!=0){
+                 for(int i=0;i<channels;i++) { // It's faster in this order
+                   int src=_index[i];
+                   int dest=i;
+                   for(int j=0;j<samples;j++) {
+                     val=(int)(pcm[i][src+j]*32768. + 0.5);
+                     if(val>32767)val=32767;
+                     else if(val<-32768)val=-32768;
+                     buffer[dest]=(byte)(val>>>8);
+                     buffer[dest+1]=(byte)(val);
+                     dest+=channels*2;
+                   }
+                 }
+               }
+               else{
+                 for(int i=0;i<channels;i++) {
+                   float[] src=pcm[i];
+                   int dest=i;
+                   for(int j=0;j<samples;j++) {
+                     val=(int)(src[j]*32768. + 0.5);
+                     if(val>32767)val=32767;
+                     else if(val<-32768)val=-32768;
+                     buffer[dest]=(byte)((val+off)>>>8);
+                     buffer[dest+1]=(byte)(val+off);
+                     dest+=channels*2;
+                   }
+                 }
+               }
+             }
+             else if(bigendianp!=0){
+               for(int j=0;j<samples;j++){
+                 for(int i=0;i<channels;i++){
+                   val=(int)(pcm[i][j]*32768. + 0.5);
+                   if(val>32767)val=32767;
+                   else if(val<-32768)val=-32768;
+                   val+=off;
+                   buffer[index++]=(byte)(val>>>8);
+                   buffer[index++]=(byte)val;
+                 }
+               }
+             }
+             else{
+               //int val;
+               for(int j=0;j<samples;j++){
+                 for(int i=0;i<channels;i++){
+                   val=(int)(pcm[i][j]*32768. + 0.5);
+                   if(val>32767)val=32767;
+                   else if(val<-32768)val=-32768;
+                   val+=off;
+                   buffer[index++]=(byte)val;
+                   buffer[index++]=(byte)(val>>>8);
+                 }
+               }
+             }
+           }
+         }
+       
+         vd.synthesis_read(samples);
+         pcm_offset+=samples;
+         if(bitstream!=null)bitstream[0]=current_link;
+         return(samples*bytespersample);
+       }
+      }
+
+      // suck in another packet
+      switch(process_packet(1)){
+      case 0:
+       return(0);
+      case -1:
+       return -1;
+      default:
+       break;
+      }
+    }
+  }
+
+  public int getLinks(){return links;}
+  public Info[] getInfo(){return vi;}
+  public Comment[] getComment(){return vc;}
+
+  public static void main(String[] arg){
+    try{
+      VorbisFile foo=new VorbisFile(arg[0]);
+      int links=foo.getLinks();
+      System.out.println("links="+links);
+      Comment[] comment=foo.getComment();
+      Info[] info=foo.getInfo();
+      for(int i=0; i<links; i++){
+        System.out.println(info[i]);
+        System.out.println(comment[i]);
+      }
+      System.out.println("raw_total: "+foo.raw_total(-1));
+      System.out.println("pcm_total: "+foo.pcm_total(-1));
+      System.out.println("time_total: "+foo.time_total(-1));
+    }
+    catch(Exception e){
+      System.err.println(e);
+    }
+  }
+}
diff --git a/lang/de.js b/lang/de.js
new file mode 100644 (file)
index 0000000..64beffc
--- /dev/null
@@ -0,0 +1,100 @@
+/* these files need to be written in utf-8 */
+
+// Messages
+LANG.VOLUME            ='Lautstärke';
+LANG.BITRATE           ='Bitrate: ';
+LANG.POSITION          ='Position: ';
+LANG.CROP              ='Ausschneiden'; 
+LANG.CROP_SELECTION    ="Selektierte ausschneiden";
+LANG.CLEAR_PLAYLIST    ="Playlist leeren";
+
+LANG.WAIT_LOADING      ="Lade.."; 
+LANG.WAIT_UPDATING_DB  ="Aktualisiere Datenbank.."
+LANG.WAIT_UPDATING_PL  ="Aktualisiere Playlist, Bitte warten..";
+LANG.WAIT_REMOVING     ="Entferne..";
+LANG.WAIT_ADDING       ="Füge hinzu..";
+LANG.WAIT_ADDING_PL    ="Füge Playlist hinzu..";
+LANG.WAIT_SEARCHING    ="Suche..";
+
+LANG.UPDATE_DB         ="Aktualisiere DB";
+LANG.ARTIST            ="Künstler";
+LANG.TITLE             ="Titel";
+LANG.ALBUM             ="Album";
+LANG.GENRE             ="Genre";
+LANG.FILENAME          ="Dateiname";
+LANG.FILESYSTEM                ="Dateisystem";
+LANG.LYRICS            ="Songtext";
+LANG.SEARCH            ="Suchen";
+LANG.ADD               ="Hinzufügen";
+LANG.EDIT              ="Bearbeiten";
+LANG.DELETE            ="Löschen";
+LANG.CONFIRM_REMOVE    ="Wirklich löschen";
+LANG.YES               ="Ja";
+LANG.NO                        ="Nein";
+LANG.BY_URL            ="von URL";
+LANG.FROM_FILE         ="von Datei";
+LANG.TEXT              ="Text";
+LANG.OUTPUTS           ="Ausgabe";
+LANG.CLOSE             ="Schließen";
+LANG.SAVE              ="Speichern";
+LANG.REFETCH           ="Erneuern";
+LANG.HIDE              ="Verstecken";
+LANG.AUTOPLAY          ="automatisch abspielen";
+LANG.NO_AUTOPLAY       ="nicht automatisch abspielen";
+LANG.STREAMING         ="Streaming;
+
+LANG.ANYTAG            ="Jedes Feld";
+LANG.COMPOSER          ="Verfasser";
+LANG.PERFORMER         ="Darsteller"; 
+LANG.DATE              ="Datum"; 
+
+LANG.PL_SAVE_AS        ="Speichere Playlist als: ";
+LANG.PL_SAVING         ="Speichere Playlist";
+
+LANG.REPEAT            ="Wiederholen";
+LANG.RANDOM            ="Zufallswiedergabe";
+LANG.XFADE             ="Ãœberblenden: ";
+
+LANG.QUICK_ADD         ="Schnell hinzufügen";
+
+LANG.ALBUM_REVIEW      ="Album Bewertung";
+LANG.ALBUM_DESC                ="Album Beschreibung";
+// e.g. album review for some album by some artist, the spaces are important
+LANG.ALBUM_AA_NAME     =" für %s von %s"; 
+LANG.ALBUM_AMAZON      ="Album auf amazon.com (neues Fenster)";
+
+LANG.JUMP_CURRENT      = "Springe zum aktuell spielenden Lied [Leertaste]";
+LANG.PAGINATION_FOLLOW = "Folge aktuell gespieltes Lied";
+LANG.PAGINATION_NOFOLLOW= "Folge NICHT aktuell gespieltes Lied";
+
+LANG.LYRICWIKI_LYRIC   = "%s Songtexte von lyricwiki.org";  // add/edit lyric at ..
+
+// ERRORS
+LANG.E_CONNECT         ="Verbindung mit dem MPD Server ist nicht möglich";
+LANG.E_INVALID_RESPONSE        ="Server Antwortete ungültig";
+LANG.E_INVALID_RESULT  ="Ungültiges Resultat vom Server";
+LANG.E_NO_RESPONSE     ="Bekomme keine Antwort vom Server";
+LANG.E_CONNECT         ="Verbindung mit mpd nicht möglich";
+LANG.E_INIT            ="Initialisierung schlug fehl "
+LANG.E_INIT_PL         ="Fehler beim initialisieren der Playlist";
+LANG.E_PL_MOVE         ="Dehler beim verschieben in der Playlist";
+LANG.E_REMOVE          ="Konnte Lieder nicht entfernen.";
+LANG.E_FAILED_ADD      ="Fehler beim hinzufügen";
+LANG.E_FAILED_ADD_PL   ="Fehler beim hinzufügen der Playlist";
+LANG.E_FAILED_SAVE_PL  ="Fehler beim speichern der Playlist";
+LANG.E_FAILED_LOAD_DIR ="Fehler beim laden der Verzeichnisliste";
+LANG.E_NOTHING_FOUND   ="Nichts gefunden..";
+LANG.E_NO_OUTPUTS      ="Keine Ausgaben gefunden";
+LANG.E_NOT_FOUND       ="Habe keine %ss gefunden."; // We didn't find any of these
+LANG.E_MISSING_CACHE   ="Fehlendes Cache Verzeichnis";
+LANG.E_MISSING_AA_NAME ="Künstler oder Albumname fehlt.";
+LANG.E_MISSING_AS_NAME ="Künstler oder Liedtitel fehlt.";
+LANG.E_LYRICS_NOT_FOUND        ="Liedtext nicht gefunden";
+LANG.E_MISSING_LYRICS  ="Es scheint so, als würden die Liedtexte fehlen.."; // this should be something better
+LANG.E_LYRICS_FAILURE  ="Die Abfrage der Liedtexte schluge fehl.";
+LANG.E_COMM_PROBLEM    ="Verbindungsproblem";
+LANG.E_GET_INFO                ="Kann keine Information erhalten";
+
+
+/* Don't need translation, but needs to be here: */
+LANG.NT_AMAZON = "[Amazon.com]";
diff --git a/lang/de.php b/lang/de.php
new file mode 100644 (file)
index 0000000..5bfd241
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+       /* These files also has to be written in utf-8 */
+       /* Translated by Christian Fischer (Contact: flyinghuman@web.de) */
+
+       $LANG['de'] = array( 
+               /* index.php */
+               'Quick add' => 'Schnell hinzufügen',
+               'Server settings' => 'Server Einstellungen',
+               'Add playlists or audio-files' => 'Füge Playlisten oder Audiodateien hinzu',
+               'Add playlist' => 'Füge Playlist hinzu',
+               'Save current playlist' => 'Speichere aktive Playlist',
+               'Save playlist' => 'Speichere Playlist',
+               'Search current playlist' => 'Suche in aktiver Playlist',
+               'Search playlist' => 'Suche in Playlist',
+               'Configure' => 'Konfigurieren',
+               'Start streaming to browser' => 'Starte streaming zum Browser',
+               'Streaming' => 'Streaming',
+               'Get more information about this song/album' => 'Erhalte mehr Informationen Ã¼ber dieses Lied/Album',
+               'Song Info' => 'Lied-Infos',
+               'Crop to selection' => 'Ausgewählte ausschneiden',
+               'Remove selection' => 'Ausgewählte entfernen',
+               'Open directory' => 'Verzeichnis Ã¶ffnen',
+               'Lyrics' => 'Lyrik',
+               'Album description' => 'Beschreibung',
+               'Album review' => 'Bewertung',
+               'Close' => 'Schließen',
+
+               /* configure.php */
+               'Pitchfork configuration' => 'Pitchfork Einstellungen',
+               'Pitchfork MPD Client Configuration' => 'Pitchfork MPD-Klient Konfiguration',
+               'Configure settings' => 'Einstellungen festlegen',
+               'Connection-settings' => 'Verbindungseinstellungen',
+               'Where can I find your MPD-server?' => 'Wo kann ich den MPD-Server erreichen?',
+               'Hostname:' => 'Host:',
+               'Port:' => 'Port:',
+               'Password:' => 'Passwort:',
+               'User interface' => 'Benutzeroberfläche',
+               'Some other settings!' => 'Sonstige Einstellungen...',
+               'Update time:' => 'Aktualisierungsintervall:',
+               'How often we should request updates from the server' => 'Wie oft sollen die Aktualisierungen vom Server geholt werden?',
+               'Login password (optional):' => 'Login Passwort (optional):',
+               'If you want to require a password to see these pages you may specify it here' => 
+                       'Wenn Sie diese Seite nur durch Eingabe eines Passworts sehen wollen, so geben Sie dies hier ein.',
+               'Theme:' => 'Thema:',
+               'Language:' => 'Sprache:',
+               'Include stop button:' => 'Zeige Stopptaste:',
+               'Pagination:' => 'Nummerierung:',
+               'Maximum number of entries pr. page. Set to 0 to disable.' => 'Maximale Anzahl der Einträge auf einer Seite. Auf 0 stellen um dies auszustellen.',
+               'Show these fields in the playlist:' => 'Diese Felder in der Playlist anzeigen:',
+               'Position' => 'Position',
+               'Show nonstandard' => 'Zeige Felder, die nicht Standard sind',
+               'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => 
+                       'Einstellungen zum empfangen der Metadaten. Dies benötigt eine Internetverbindung auf dem Rechner, wo Pitchfork eingesetzt wird.',
+               'Disable metadata:' => 'Metadaten nicht benutzen:',
+               'Shoutcast integration' => 'Shoutcast Integration',
+               'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => 
+                       'Spezifiziere hier optional die URL zu dem Shout-Stream des MPD, um eine Integration in Pitchfork zu ermöglichen.',
+               'Pitchfork info' => 'Pitchfork Informationen',
+               'Release version:' => 'Version:',
+               'Release date:' => 'Datum:',
+               'Connect to mpd:' => 'Verbindung zum mpd:',
+               'Yes' => 'Ja',
+               'No' => 'Nein',
+               'MPD commands:' => 'MPD Kommandos:',
+               'Metadata directory:' => 'Metadaten Verzeichnis:',
+               'Functions:' => 'Funktionen:',
+               'PHP memory limit:' => 'PHP Speicherlimit:'
+       );
+?>
diff --git a/lang/en.js b/lang/en.js
new file mode 100644 (file)
index 0000000..c227ecd
--- /dev/null
@@ -0,0 +1,112 @@
+/* these files need to be written in utf-8 */
+
+var LANG = new Object(); 
+// Messages
+LANG.VOLUME            ='Volume';
+LANG.BITRATE           ='Bitrate: ';
+LANG.POSITION          ='Position: ';
+LANG.CROP              ='Crop'; 
+LANG.CROP_SELECTION    ="Crop to selection";
+LANG.CLEAR_PLAYLIST    ="Clear playlist";
+
+LANG.PLAY              ="Play";
+LANG.STOP              ="Stop";
+
+
+LANG.WAIT_LOADING      ="Loading.."; 
+LANG.WAIT_UPDATING_DB  ="Updating database.."
+LANG.WAIT_UPDATING_PL  ="Updating playlist, please wait..";
+LANG.WAIT_REMOVING     ="Removing..";
+LANG.WAIT_ADDING       ="Adding..";
+LANG.WAIT_ADDING_PL    ="Adding playlist..";
+LANG.WAIT_SEARCHING    = "Searching..";
+
+LANG.UPDATE_DB         ="Update DB";
+LANG.ARTIST            ="Artist";
+LANG.TITLE             ="Title";
+LANG.ALBUM             ="Album";
+LANG.GENRE             ="Genre";
+LANG.FILENAME          ="Filename";
+LANG.FILESYSTEM                ="Filesystem";
+LANG.LYRICS            ="Lyrics";
+LANG.SEARCH            ="Search";
+LANG.ADD               ="Add";
+LANG.EDIT              ="Edit";
+LANG.DELETE            ="Delete";
+LANG.CONFIRM_REMOVE    ="Really remove";
+LANG.YES               ="Yes";
+LANG.NO                        ="No";
+LANG.BY_URL            ="By URL";
+LANG.FROM_FILE         ="From file";
+LANG.TEXT              ="Text";
+LANG.OUTPUTS           ="Outputs";
+LANG.CLOSE             ="Close";
+LANG.SAVE              ="Save";
+LANG.REFETCH           ="Refetch";
+LANG.HIDE              ="Hide";
+LANG.AUTOPLAY          ="Autoplay";
+LANG.NO_AUTOPLAY       ="No autoplay";
+LANG.STREAMING         ="Streaming";
+
+LANG.ANYTAG            ="Any tag";
+LANG.COMPOSER          ="Composer";
+LANG.PERFORMER         ="Performer";
+LANG.DATE              ="Date";
+
+
+LANG.PL_SAVE_AS        ="Save playlist as: ";
+LANG.PL_SAVING         ="Saving playlist";
+
+LANG.REPEAT            ="Repeat";
+LANG.RANDOM            ="Random";
+LANG.XFADE             ="X-Fade: ";
+
+LANG.QUICK_ADD         ="Quick add";
+
+LANG.ALBUM_REVIEW      ="Album review";
+LANG.ALBUM_DESC                ="Album description";
+// e.g. album review for some album by some artist, the spaces are important
+LANG.ALBUM_AA_NAME     =" for %s by %s"; 
+LANG.ALBUM_AMAZON      ="Album on amazon.com (new window)";
+
+LANG.JUMP_CURRENT      = "Jump to currently playing song [Space]";
+LANG.PAGINATION_FOLLOW = "Following currently playing song";
+LANG.PAGINATION_NOFOLLOW= "Not following currently playing song";
+
+LANG.LYRICWIKI_LYRIC   = "%s lyric at lyricwiki.org";  // add/edit lyric at ..
+
+// ERRORS
+LANG.E_CONNECT         ="Unable to connect to MPD server";
+LANG.E_INVALID_RESPONSE        ="Server returned invalid response";
+LANG.E_INVALID_RESULT  ="Invalid result from server";
+LANG.E_NO_RESPONSE     ="Unable to get response from server";
+LANG.E_CONNECT         ="Unable to connect to mpd";
+LANG.E_INIT            ="Init failed "
+LANG.E_INIT_PL         ="Failed to initialize playlist";
+LANG.E_PL_MOVE         ="Moving in playlist failed";
+LANG.E_REMOVE          ="Could not remove songs";
+LANG.E_FAILED_ADD      ="Failed to add";
+LANG.E_FAILED_ADD_PL   ="Failed to add playlist";
+LANG.E_FAILED_SAVE_PL  ="Failed to save playlist";
+LANG.E_FAILED_LOAD_DIR ="Failed to load directory list";
+LANG.E_NOTHING_FOUND   ="Nothing found..";
+LANG.E_NO_OUTPUTS      ="No outputs found";
+LANG.E_NOT_FOUND       ="Did not find any %ss."; // We didn't find any of these
+LANG.E_MISSING_CACHE   ="Missing cache dir";
+LANG.E_MISSING_AA_NAME ="Missing artist or album name.";
+LANG.E_MISSING_AS_NAME ="Missing artist or song-title.";
+LANG.E_LYRICS_NOT_FOUND        ="Lyrics not found";
+LANG.E_MISSING_LYRICS  ="We seem to be missing the lyrics.."; // this should be something better
+LANG.E_LYRICS_FAILURE  ="Retrieval of lyrics failed";
+LANG.E_COMM_PROBLEM    ="Communication problem";
+LANG.E_GET_INFO                ="Unable to get information";
+
+LANG.RECOMMEND_RECOMMENDATIONS = "Recommendations ";
+LANG.RECOMMEND_EMPTY_PLAYLIST  ="To fetch recommendations based on your playlist you need to have something in your playlist";
+LANG.RECOMMEND_ADDTOPLAYLIST   ="Add to playlist";
+LANG.RECOMMEND_SIMILAR         ="Similar artists from your library";
+LANG.RECOMMEND_ARTISTS         ="Recommended artists";
+
+
+/* Don't need translation, but needs to be here: */
+LANG.NT_AMAZON = "[Amazon.com]";
diff --git a/lang/en.php b/lang/en.php
new file mode 100644 (file)
index 0000000..0f5d1a7
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+       /* These files also has to be written in utf-8 */
+
+       $LANG['en'] = array( 
+               /* index.php */
+               'Quick add' => 'Quick add',
+               'Server settings' => 'Server settings',
+               'Add playlists or audio-files' => 'Add playlists or audio-files',
+               'Add playlist' => 'Add playlist',
+               'Save current playlist' => 'Save current playlist',
+               'Save playlist' => 'Save playlist',
+               'Search current playlist' => 'Search current playlist',
+               'Search playlist' => 'Search playlist',
+               'Configure' => 'Configure',
+               'Start streaming to browser' => 'Start streaming to browser',
+               'Streaming' => 'Streaming',
+               'Get more information about this song/album' => 'Get more information about this song/album',
+               'Song Info' => 'Song Info',
+               'Crop to selection' => 'Crop to selection',
+               'Remove selection' => 'Remove selection',
+               'Open directory' => 'Open directory',
+               'Lyrics' => 'Lyrics',
+               'Album description' => 'Album description',
+               'Album review' => 'Album review',
+               'Close' => 'Close',
+
+               /* configure.php */
+               'Pitchfork MPD Client Configuration' => 'Pitchfork MPD Client Configuration',
+               'Configure settings' => 'Configure settings',
+               'Connection-settings' => 'Connection-settings',
+               'Where can I find your MPD-server?' => 'Where can I find your MPD-server?',
+               'Hostname:' => 'Hostname:',
+               'Port:' => 'Port:',
+               'Password:' => 'Password:',
+               'User interface' => 'User interface',
+               'Some other settings!' => 'Some other settings!',
+               'Update time:' => 'Update time:',
+               'How often we should request updates from the server' => 'How often we should request updates from the server',
+               'Login password (optional):' => 'Login password (optional):',
+               'If you want to require a password to see these pages you may specify it here' => 
+                       'If you want to require a password to see these pages you may specify it here',
+               'Theme:' => 'Theme:',
+               'Language:' => 'Language:',
+               'Include stop button:' => 'Include stop button:',
+               'Pagination:' => 'Pagination:',
+               'Maximum number of entries pr. page. Set to 0 to disable.' => 'Maximum number of entries pr. page. Set to 0 to disable.',
+               'Show these fields in the playlist:' => 'Show these fields in the playlist:',
+               'Position' => 'Position',
+               'Show nonstandard' => 'Show nonstandard',
+               'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => 
+                       'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.',
+               'Disable metadata:' => 'Disable metadata:',
+               'Shoutcast integration' => 'Shoutcast integration',
+               'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => 
+                       'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.',
+               'Pitchfork info' => 'Pitchfork info',
+               'Release version:' => 'Release version:',
+               'Release date:' => 'Release date:',
+               'Connect to mpd:' => 'Connect to mpd:',
+               'Yes' => 'Yes',
+               'No' => 'No',
+               'MPD commands:' => 'MPD commands:',
+               'Metadata directory:' => 'Metadata directory:',
+               'Functions:' => 'Functions:',
+               'PHP memory limit:' => 'PHP memory limit:'
+       );
+?>
diff --git a/lang/eu.js b/lang/eu.js
new file mode 100644 (file)
index 0000000..1c644ef
--- /dev/null
@@ -0,0 +1,106 @@
+/* these files need to be written in utf-8 */
+
+// Messages
+LANG.VOLUME            ='Bolumena';
+LANG.BITRATE           ='Bit-tasa: ';
+LANG.POSITION          ='Posizioa: ';
+LANG.CROP              ='Eskilatu'; 
+LANG.CROP_SELECTION    ="Aukeraketa bakarrik";
+LANG.CLEAR_PLAYLIST    ="Garbitu zerrenda";
+
+LANG.WAIT_LOADING      ="Zabaltzen..."; 
+LANG.WAIT_UPDATING_DB  ="Datubasea eguneratzen..."
+LANG.WAIT_UPDATING_PL  ="Zerrenda eguneratzen, ixaron mesedez...";
+LANG.WAIT_REMOVING     ="Ezabatzen...";
+LANG.WAIT_ADDING       ="Gehitzen...";
+LANG.WAIT_ADDING_PL    ="Zerrenda gehitzen...";
+LANG.WAIT_SEARCHING    ="Bilatzen...";
+
+LANG.UPDATE_DB         ="DB Eguneratu";
+LANG.ARTIST            ="Artista";
+LANG.TITLE             ="Titulua";
+LANG.ALBUM             ="Albuma";
+LANG.GENRE             ="Generoa";
+LANG.FILENAME          ="Fitxategia";
+LANG.FILESYSTEM                ="Filesystem";
+LANG.LYRICS            ="Letrak";
+LANG.SEARCH            ="Bilatu";
+LANG.ADD               ="Gehitu";
+LANG.EDIT              ="Editatu";
+LANG.DELETE            ="Ezabatu";
+LANG.CONFIRM_REMOVE    ="Erabat ezabatu";
+LANG.YES               ="Bai";
+LANG.NO                        ="Ez";
+LANG.BY_URL            ="URL bidez";
+LANG.FROM_FILE         ="Fitxategitik";
+LANG.TEXT              ="Textua";
+LANG.OUTPUTS           ="Irteerak";
+LANG.CLOSE             ="Itxi";
+LANG.SAVE              ="Gorde";
+LANG.REFETCH           ="Refetch";
+LANG.HIDE              ="Ezkutatu";
+LANG.AUTOPLAY          ="Autoplay";
+LANG.NO_AUTOPLAY       ="No autoplay";
+LANG.STREAMING         ="Streaming";
+
+LANG.ANYTAG            ="Edozein etiketa";
+LANG.COMPOSER          ="Egilea";
+LANG.PERFORMER         ="Eraldatzailea";
+LANG.DATE              ="Data";
+
+
+LANG.PL_SAVE_AS        ="Gorde zerrenda: ";
+LANG.PL_SAVING         ="Zerrenda gordetzen";
+
+LANG.REPEAT            ="Errepikatu";
+LANG.RANDOM            ="Ausazkoa";
+LANG.XFADE             ="X-Fade: ";
+
+LANG.QUICK_ADD         ="Gehitu azkar";
+
+LANG.ALBUM_REVIEW      ="Albumaren errebisioa";
+LANG.ALBUM_DESC                ="Albumaren deskribapena";
+// e.g. album review for some album by some artist, the spaces are important
+LANG.ALBUM_AA_NAME     =" for %s by %s"; 
+LANG.ALBUM_AMAZON      ="Album on amazon.com (new window)";
+
+LANG.JUMP_CURRENT      = "Oraingo abestira salto egin [Space]";
+LANG.PAGINATION_FOLLOW = "Oraingo abestia jarraitu";
+LANG.PAGINATION_NOFOLLOW= "Oraingo abestia ez jarraitu";
+
+LANG.LYRICWIKI_LYRIC   = "%s lyric at lyricwiki.org";  // add/edit lyric at ..
+
+// ERRORS
+LANG.E_CONNECT         ="Ezin da MPD zerbitzarira konektatu";
+LANG.E_INVALID_RESPONSE        ="Zerbitzariaren okerreko erantzuna";
+LANG.E_INVALID_RESULT  ="Zerbitzariaren okerreko emaitza";
+LANG.E_NO_RESPONSE     ="Zerbitzariak ez du erantzuten";
+LANG.E_CONNECT         ="MPD-ra ezin izan da konektatu";
+LANG.E_INIT            ="Hasieratzean hutsegitea"
+LANG.E_INIT_PL         ="Zerrenda hasieratzean hutsegitea";
+LANG.E_PL_MOVE         ="Zerrendaren aldaketak huts egin du";
+LANG.E_REMOVE          ="Ezin izan dira abestiak ezabatu";
+LANG.E_FAILED_ADD      ="Errorea gehitzerakoan";
+LANG.E_FAILED_ADD_PL   ="Errorea zerrenda gehitzerakoan";
+LANG.E_FAILED_SAVE_PL  ="Errorea zerrenda gordetzerakoan";
+LANG.E_FAILED_LOAD_DIR ="Failed to load directory list";
+LANG.E_NOTHING_FOUND   ="Ez da ezer aurkitu...";
+LANG.E_NO_OUTPUTS      ="Irteerarik ez da aurkitu";
+LANG.E_NOT_FOUND       ="Ez da aurkitu %ss."; // We didn't find any of these
+LANG.E_MISSING_CACHE   ="Cache direktorioa ez da aurkitu";
+LANG.E_MISSING_AA_NAME ="Artistaren edo albumaren izena ez da aurkitu";
+LANG.E_MISSING_AS_NAME ="Artistaren edo abestiaren izena ez da aurkitu";
+LANG.E_LYRICS_NOT_FOUND        ="Letrak ez dira aurkitu";
+LANG.E_MISSING_LYRICS  ="We seem to be missing the lyrics.."; // this should be something better
+LANG.E_LYRICS_FAILURE  ="Letrak jasotzean huts egin du";
+LANG.E_COMM_PROBLEM    ="Komunikazio arazoa";
+LANG.E_GET_INFO                ="Informaziorik ezin izan da jaso";
+
+LANG.RECOMMEND_RECOMMENDATIONS  ="Gomendioak ";
+LANG.RECOMMEND_EMPTY_PLAYLIST    ="Zerrendan abestiren bat behar duzu gomendioak jasotzeko";
+LANG.RECOMMEND_ADDTOPLAYLIST    ="Gehitu uneko zerrendara";
+LANG.RECOMMEND_SIMILAR          ="Zure liburutegitik antzeko artistak:";
+LANG.RECOMMEND_ARTISTS          ="Gomendatutako artistak:";
+
+/* Don't need translation, but needs to be here: */
+LANG.NT_AMAZON = "[Amazon.com]";
diff --git a/lang/eu.php b/lang/eu.php
new file mode 100644 (file)
index 0000000..9a9b71f
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+       /* These files also has to be written in utf-8 */
+
+       $LANG['eu'] = array( 
+               /* index.php */
+               'Quick add' => 'Gehitu azkar',
+               'Server settings' => 'Zerbitzariaren aukerak',
+               'Add playlists or audio-files' => 'Gehitu zerrenda edo fitxategia',
+               'Add playlist' => 'Gehitu zerrenda',
+               'Save current playlist' => 'Gorde zerrenda hau',
+               'Save playlist' => 'Gorde zerrenda',
+               'Search current playlist' => 'Bilatu zerrenda honetan',
+               'Search playlist' => 'Bilatu zerrendan',
+               'Configure' => 'Hobespenak',
+               'Start streaming to browser' => 'Hasi nabigatzailera streaming egiten',
+               'Streaming' => 'Streaming',
+               'Get more information about this song/album' => 'Album/abesti honi buruz informazio gehiago',
+               'Song Info' => 'Abestiari buruz',
+               'Crop to selection' => 'Aukeraketa bakarrik',
+               'Remove selection' => 'Ezabatu aukeraketa',
+               'Open directory' => 'Zabaldu direktorioa',
+               'Lyrics' => 'Letrak',
+               'Album description' => 'Albumaren deskribapena',
+               'Album review' => 'Albumaren errebisioa',
+               'Close' => 'Itxi',
+
+               /* configure.php */
+               'Pitchfork MPD Client Configuration' => 'Pitchfork MPD Bezeroaren Hobespenak',
+               'Configure settings' => 'Aldatu hobespenak',
+               'Connection-settings' => 'Konexioaren hobespenak',
+               'Where can I find your MPD-server?' => 'Nun aurkitu dezaket zure MPD-zerbitzaria?',
+               'Hostname:' => 'Helbidea:',
+               'Port:' => 'Portua:',
+               'Password:' => 'Pasahitza:',
+               'User interface' => 'Itxura',
+               'Some other settings!' => 'Beste hobespen batzuk!',
+               'Update time:' => 'Eguneratze denbora:',
+               'How often we should request updates from the server' => 'Zenbatean bein eguneratu behar da zerbitzaritik',
+               'Login password (optional):' => 'Sartzeko pasahitza (aukerazkoa):',
+               'If you want to require a password to see these pages you may specify it here' => 
+                       'Orri hauek ikusi ahal izateko pasahitza eskatzea nahi baduzu',
+               'Theme:' => 'Gaia:',
+               'Language:' => 'Hizkuntza:',
+               'Include stop button:' => 'Gehitu gelditzeko botoia:',
+               'Pagination:' => 'Pajinazioa:',
+               'Maximum number of entries pr. page. Set to 0 to disable.' => 'Orriko gehienezko fila kopurua. Jarri 0 desgaitzeko',
+               'Show these fields in the playlist:' => 'Azaldu datu hauek zerrendan:',
+               'Position' => 'Posizioa',
+               'Show nonstandard' => 'Bestelakoak',
+               'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => 
+                       'Metadata lortzeko konfigurazioa. Pitchfork exekutatzen ari den makinak interneterako atzipena behar du',
+               'Disable metadata:' => 'Desgaitu metadata:',
+               'Shoutcast integration' => 'Shoutcast integrazioa',
+               'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => 
+                       'Nahi baduzu ezarri shoutstream iturriaren URL helbidea',
+               'Pitchfork info' => 'Pitchfork info',
+               'Release version:' => 'Bertsioa:',
+               'Release date:' => 'Eguneraketa data:',
+               'Connect to mpd:' => 'MPDra konektatua:',
+               'Yes' => 'Bai',
+               'No' => 'Ez',
+               'MPD commands:' => 'MPD komandoak:',
+               'Metadata directory:' => 'Metadata direktorioa:',
+               'Functions:' => 'Funtzioak:',
+               'PHP memory limit:' => 'PHP memoria limitea:'
+       );
+?>
diff --git a/lang/fr.js b/lang/fr.js
new file mode 100644 (file)
index 0000000..c6f69fa
--- /dev/null
@@ -0,0 +1,101 @@
+/* these files need to be written in utf-8 */
+
+// Messages
+LANG.VOLUME            ='Volume';
+LANG.BITRATE           ='Débit: ';
+LANG.POSITION          ='Position: ';
+LANG.CROP              ='Conserver'; 
+LANG.CROP_SELECTION    ="Ne conserver que la sélection";
+LANG.CLEAR_PLAYLIST    ="Effacer la playlist";
+
+LANG.WAIT_LOADING      ="Chargement.."; 
+LANG.WAIT_UPDATING_DB  ="MAJ de la base de données.."
+LANG.WAIT_UPDATING_PL  ="MAJ de la playlist, patientez SVP..";
+LANG.WAIT_REMOVING     ="Suppression..";
+LANG.WAIT_ADDING       ="Ajout..";
+LANG.WAIT_ADDING_PL    ="Ajout de la playlist..";
+LANG.WAIT_SEARCHING    = "Recherche..";
+
+LANG.UPDATE_DB         ="MAJ BD";
+LANG.ARTIST            ="Artiste";
+LANG.TITLE             ="Titre";
+LANG.ALBUM             ="Album";
+LANG.GENRE             ="Genre";
+LANG.FILENAME          ="Fichier";
+LANG.FILESYSTEM                ="Système de fichiers";
+LANG.LYRICS            ="Paroles";
+LANG.SEARCH            ="Recherche";
+LANG.ADD               ="Ajout";
+LANG.EDIT              ="Édition";
+LANG.DELETE            ="Suppression";
+LANG.CONFIRM_REMOVE    ="Confirmez la suppression";
+LANG.YES               ="Oui";
+LANG.NO                        ="Non";
+LANG.BY_URL            ="Par URL";
+LANG.FROM_FILE         ="À partir d'un fichier";
+LANG.TEXT              ="Texte";
+LANG.OUTPUTS           ="Sortie";
+LANG.CLOSE             ="Fermer";
+LANG.SAVE              ="Enregistrer";
+LANG.REFETCH           ="Récupérer Ã  nouveau";
+LANG.HIDE              ="Cacher";
+LANG.AUTOPLAY          ="Lecture auto";
+LANG.NO_AUTOPLAY       ="Pas de lecture auto";
+LANG.STREAMING         ="Flux";
+
+LANG.ANYTAG            ="N'importe quelle Ã©tiquette";
+LANG.COMPOSER          ="Compositeur";
+LANG.PERFORMER         ="Interprète";
+LANG.DATE              ="Date";
+
+
+LANG.PL_SAVE_AS        ="Enregistrer la playlist sous: ";
+LANG.PL_SAVING         ="Enregistrement de la playlist";
+
+LANG.REPEAT            ="Répéter";
+LANG.RANDOM            ="Aléatoire";
+LANG.XFADE             ="Crossfade: ";
+
+LANG.QUICK_ADD         ="Ajout rapide";
+
+LANG.ALBUM_REVIEW      ="Revue de l'album";
+LANG.ALBUM_DESC                ="Description de l'album";
+// e.g. album review for some album by some artist, the spaces are important
+LANG.ALBUM_AA_NAME     =" %s par %s"; 
+LANG.ALBUM_AMAZON      ="Album sur amazon.com (nouvelle fenêtre)";
+
+LANG.JUMP_CURRENT      = "Aller Ã  la chanson en cours [Espace]";
+LANG.PAGINATION_FOLLOW = "Suivre la chanson en cours";
+LANG.PAGINATION_NOFOLLOW= "Ne pas suivre la chanson en cours";
+
+LANG.LYRICWIKI_LYRIC   = "paroles de %s sur lyricwiki.org";  // add/edit lyric at ..
+
+// ERRORS
+LANG.E_CONNECT         ="Impossible de se connecter au serveur MPD";
+LANG.E_INVALID_RESPONSE        ="Le serveur a renvoyé une réponse non valide";
+LANG.E_INVALID_RESULT  ="Résultat non valide du serveur";
+LANG.E_NO_RESPONSE     ="Impossible d'avoir une réponse du serveur";
+LANG.E_CONNECT         ="Impossible de se connecter Ã  mpd";
+LANG.E_INIT            ="Init a Ã©choué "
+LANG.E_INIT_PL         ="L'initialisation de la playlist a Ã©choué";
+LANG.E_PL_MOVE         ="Le déplacement dans la playlist a Ã©choué";
+LANG.E_REMOVE          ="Les chansons n'ont pas pu Ãªtre enlevées";
+LANG.E_FAILED_ADD      ="L'ajout a Ã©choué";
+LANG.E_FAILED_ADD_PL   ="L'ajout de la playlist a Ã©choué";
+LANG.E_FAILED_SAVE_PL  ="L'enregistrement de la playlist a Ã©choué";
+LANG.E_FAILED_LOAD_DIR ="Le chargement du répertoire a Ã©choué";
+LANG.E_NOTHING_FOUND   ="Aucun résultat..";
+LANG.E_NO_OUTPUTS      ="Aucune sortie trouvée";
+LANG.E_NOT_FOUND       ="Aucun %ss trouvé(e)."; // We didn't find any of these
+LANG.E_MISSING_CACHE   ="Cache manquant";
+LANG.E_MISSING_AA_NAME ="Artist ou album manquant.";
+LANG.E_MISSING_AS_NAME ="Artist or titre manquant.";
+LANG.E_LYRICS_NOT_FOUND        ="Aucune parole trouvée";
+LANG.E_MISSING_LYRICS  ="Les paroles semblent manquantes.."; // this should be something better
+LANG.E_LYRICS_FAILURE  ="L'extraction des paroles a Ã©chouée";
+LANG.E_COMM_PROBLEM    ="Problème de communication";
+LANG.E_GET_INFO                ="Récupération d'infos impossible";
+
+
+/* Don't need translation, but needs to be here: */
+LANG.NT_AMAZON = "[Amazon.com]";
diff --git a/lang/fr.php b/lang/fr.php
new file mode 100644 (file)
index 0000000..9cb1aae
--- /dev/null
@@ -0,0 +1,67 @@
+<?php
+       /* These files also has to be written in utf-8 */
+
+       $LANG['fr'] = array( 
+               /* index.php */
+               'Quick add' => 'Ajout rapide',
+               'Server settings' => 'Configuration du serveur',
+               'Add playlists or audio-files' => 'Ajout de playlists ou de fichiers',
+               'Add playlist' => 'Ajout de playlists',
+               'Save current playlist' => 'Enregistrer la playlist courante',
+               'Save playlist' => 'Enregistrer la playlist',
+               'Search current playlist' => 'Rechercher dans la playlist courante',
+               'Search playlist' => 'Rechercher dans la playlist',
+               'Configure' => 'Configurer',
+               'Start streaming to browser' => 'Démarrer le flux vers le navigateur',
+               'Streaming' => 'Flux',
+               'Get more information about this song/album' => 'Obtenir plus d infos sur la chanson/l album',
+               'Song Info' => 'Infos sur la chanson',
+               'Crop to selection' => 'Ne conserver que la sélection',
+               'Remove selection' => 'Retirer la sélection',
+               'Open directory' => 'Ouvrir la base de données',
+               'Lyrics' => 'Paroles',
+               'Album description' => 'Description de l\'album',
+               'Album review' => 'Revue de l\'album',
+               'Close' => 'Fermer',
+
+               /* configure.php */
+               'Pitchfork MPD Client Configuration' => 'Configuration du Client MPD Pitchfork',
+               'Configure settings' => 'Réglages de configuration',
+               'Connection-settings' => 'Réglages de connection',
+               'Where can I find your MPD-server?' => 'Où se trouve le serveur MPD?',
+               'Hostname:' => 'Hôte:',
+               'Port:' => 'Port:',
+               'Password:' => 'Mot de passe:',
+               'User interface' => 'Interface utilisateur',
+               'Some other settings!' => 'D\'autres réglages!',
+               'Update time:' => 'Horaire de MAJ:',
+               'How often we should request updates from the server' => 'Avec quelle fréquence faut-il mettre Ã  jour le serveur',
+               'Login password (optional):' => 'Mot de passe de connection (optionnel):',
+               'If you want to require a password to see these pages you may specify it here' => 
+                       'Si vous souhaiter demander un mot de passe vous pouvez le spécifier ici',
+               'Theme:' => 'Thème:',
+               'Language:' => 'Langue:',
+               'Include stop button:' => 'Inclure le bouton stop:',
+               'Pagination:' => 'Pagination:',
+               'Maximum number of entries pr. page. Set to 0 to disable.' => 'Nombre maximum d\'entrées par page. Régler Ã  0 pour désactiver.',
+               'Show these fields in the playlist:' => 'Afficher ces champs dans la playlist:',
+               'Position' => 'Position',
+               'Show nonstandard' => 'Afficher les non-standard',
+               'Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet.' => 
+                       'Configuration pour l\'extraction des metadatas. Ceci nécessite que la machine où pitchfork est installé ait accès Ã  internet.',
+               'Disable metadata:' => 'Désactiver les metadatas:',
+               'Shoutcast integration' => 'Integration de Shoutcast',
+               'Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.' => 
+                       'Spécifier une URL pour le flux shout fourni par mpd afin d\'activer l\'intégration avec pitchfork (optionnel).',
+               'Pitchfork info' => 'Info Pitchfork',
+               'Release version:' => 'Version:',
+               'Release date:' => 'Date:',
+               'Connect to mpd:' => 'Connection Ã  mpd:',
+               'Yes' => 'Oui',
+               'No' => 'Non',
+               'MPD commands:' => 'Commandes MPD:',
+               'Metadata directory:' => 'Répertoire des metadatas:',
+               'Functions:' => 'Fonctions:',
+               'PHP memory limit:' => 'Limite mémoire de PHP:'
+       );
+?>
diff --git a/lang/master.php b/lang/master.php
new file mode 100644 (file)
index 0000000..8fe9d17
--- /dev/null
@@ -0,0 +1,37 @@
+<?php 
+       $LANG = array();
+
+       if(!isset($language))
+               $language = "en";
+
+       // just in case, even though we should probably die if we find / in it
+       $language = str_replace("/", "", $language);
+
+       require_once("../lang/" . $language . ".php");
+
+
+       function m($str) {
+               global $LANG, $language;
+
+               if(!isset($LANG[$language])) {
+                       return $str;
+               }
+               
+               if(!isset($LANG[$language][$str])) {
+       //              generate_mstring($str);
+                       return $str;
+               }
+       //      generate_mstring($str, $LANG[$language][$str]);
+               
+               return $LANG[$language][$str];
+       }
+
+       function generate_mstring($str, $res = null) {
+               global $generated_output;
+               $res = addcslashes(is_null($res)?$str:$res, '\'\\');
+               $generated_output .= 
+                       "'$str' => '$res',\n";
+
+       }
+       
+?>
diff --git a/player/command.php b/player/command.php
new file mode 100644 (file)
index 0000000..25b2575
--- /dev/null
@@ -0,0 +1,832 @@
+<?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.
+*/
+
+       
+       require_once("../inc/base.php");
+       require_once("../inc/JSON.php");
+
+       define('PF_FAILURE', 'failed');
+
+       $META_SEARCH = array("any", "Artist", "Title", "Album", "Genre", "filename", "Composer", "Performer", "Date" ); // "lyrics"...
+       $PL_SEARCH = array("any", "Artist", "Title", "Album", "Genre", "filename", "Composer", "Performer", "Date" ); // "lyrics"...
+
+       header("Content-Type: text/plain; charset=UTF-8");
+
+       function ids_to_list($sel) {
+               $arr = explode(";", trim($sel));
+               $res = array();
+               $size = count($arr);
+               foreach($arr as $val) {
+                       $pos = strpos($val, "-");
+                       if($pos === FALSE) {
+                               $res[] = $val;
+                       }
+                       else {
+                               $tmp = explode("-", $val);
+                               $res = array_merge($res, range($tmp[0], $tmp[1])); 
+                       }
+               }
+               return $res;
+       }
+
+       function selection_to_list($sel) {
+               $res = ids_to_list($sel);
+               sort($sel, SORT_NUMERIC);
+               return $res;
+       }
+               
+       function selection_to_reverse_list($sel) {
+               $res = ids_to_list($sel);
+               rsort($res, SORT_NUMERIC);
+               return $res;
+       }
+
+       function search_add($db, $pl, $search) {
+               $tmp = $db->find($search, true);
+               foreach($tmp as &$a) {
+                       $pl->addSong($a['file']);
+               }
+               if(count($tmp)>0) 
+                       return true;
+               return false;
+       }
+
+       function parsePlaylist($txt, $type = false) {
+               $txt = explode("\n", $txt); // trim will remove the \r
+               $res = array();
+               if($type=="pls"||$type===false) {
+                       foreach($txt as $t) {
+                               $t = trim($t);
+                               if(stripos($t, "file")!==false) { 
+                                       $pos = spliti("^file[0-9]*=", $t);
+                                       if(count($pos)==2&&strlen($pos[1])>0)
+                                               $res[] = $pos[1];
+                               }
+                       }
+               }                       
+               else if($type=="m3u" || ($type===false&&count($res)==0) ) {
+                       foreach($txt as $t) {
+                               $t = trim($t);
+                               if(strpos($t, "#")!==false||strlen($t)==0) {
+                                       echo "skipping: $t\n";
+                                       continue;
+                               }
+                               $res[] = $t;
+                       }
+               }
+               return $res;
+       }
+       function handle_playlist_url($pl, $url, $get = false) {
+               if($get) {
+                       $fp = @fopen($url, "r");
+                       if($fp) {
+                               $type = substr($url, strlen($url)-3); // just get three last chars..
+                               $md = stream_get_meta_data($fp);
+                               $md = $md['wrapper_data'];
+                               foreach($md as $m) {
+                                       if(stripos($m, "content-type:")==0) {
+                                               if(stripos($m, "audio/x-scpls")) {
+                                                       $typdde = "pls";
+                                               }
+                                               else if(stripos($m, "audio/x-mpegurl")
+                                                    || stripos($m, "audio/mpegurl")) {
+                                                    $type = "m3u";
+                                               }
+                                       }
+                               }
+                               $type = strtolower($type);
+                               $data = stream_get_contents($fp);
+                               $stuff = parsePlaylist($data, $type);
+                               foreach($stuff as $s) {
+                                       $pl->addSong($s);
+                               }
+                               return true;
+                       }
+                       return false;
+               }
+               else {
+                       $opts = array(
+                         'http'=>array(
+                            'method'=>"HEAD",
+                       ));
+                       $context = stream_context_create($opts);
+                       $fp = @fopen($url, "r", false, $context);
+                       $md = null;
+
+                       if(!$fp) { 
+                               $md = array(); // head did not work....
+                       }
+                       else {
+                               $md = stream_get_meta_data($fp);
+                               $md = $md['wrapper_data'];
+                       }
+
+                       $type = substr($url, strlen($url)-3); // just get three last chars..
+
+                       /* these lists are probably incomplete, make a ticket if 
+                          you want something added */
+                       foreach($md as $m) {
+                               if(stripos($m, "content-type:")==0) {
+                                       if(stripos($m, "audio/x-scpls")||
+                                          stripos($m, "audio/x-mpegurl")||
+                                          stripos($m, "audio/mpegurl")) {
+                                               return handle_playlist_url($pl, $url, true);
+                                       }
+                                       else if(stripos($m, "audio/mpeg")||
+                                               stripos($m, "audio/mpeg3")||
+                                               stripos($m, "audio/x-mpeg3")||
+                                               stripos($m, "audio/mpeg2")||
+                                               stripos($m, "audio/x-mpeg2")||
+                                               stripos($m, "application/ogg")||
+                                               stripos($m, "audio/x-ogg")||
+                                               stripos($m, "audio/mp4")||
+                                               stripos($m, "audio/x-mod")||
+                                               stripos($m, "audio/mod")||
+                                               stripos($m, "audio/basic")||
+                                               stripos($m, "audio/x-basic")||
+                                               stripos($m, "audio/wav")||
+                                               stripos($m, "audio/x-wav")||
+                                               stripos($m, "audio/flac")||
+                                               stripos($m, "audio/x-flac")
+                                               ) {
+                                               $pl->addSong($url);
+                                               return true;
+                                       }
+                               }
+                       }
+                       $type = strtolower($type);
+                       $type4 = strtolower($url, strlen($url)-4);
+                       if($type=="m3u"||$type=="pls") {
+                               return handle_playlist_url($pl, $url, true);
+                       }
+                       else if($type=="ogg"||$type=="mp3"||$type=="mp2"||$type=="wav"
+                               ||$type==".au"||$type=="m4a"||$type4=="flac"||$type4=="aiff") {
+                               // ugh, just try to add it... 
+                               $pl->addSong($url);
+                               return true;
+                       }
+               }
+               return false;
+       }
+
+       function array_to_json(&$arr) {
+               echo "(";
+               if(function_exists("json_encode")) {
+                       echo json_encode($arr);
+               } else {
+                       $json = new Services_JSON();
+                       $json->encode($arr);
+               }
+               echo ")";
+       }
+       
+       /* for auto-play-start on empty */
+       $playlist_empty = false;
+       $something_added = false;
+
+       $json = null;
+       $pl = get_playlist();
+       if(!$pl) {
+               $v = array("connection" => PF_FAILURE);
+               echo array_to_json($v);
+               exit();
+       }
+       else if(isset($_GET['add'])||isset($_GET['ma'])||isset($_GET['searchadd'])) {
+               /* for automatic playback start */
+               try {
+                       $s = $pl->getStatus();
+                       if(isset($s['playlistlength'])&&intval($s['playlistlength'])==0) {
+                               $playlist_empty = true;
+                       }
+               }
+               catch (PEAR_Exception $e) {
+                       $v = array("connection" => PF_FAILURE);
+                       echo array_to_json($v);
+                       exit();
+               }
+       }
+
+       if(isset($_GET['playlist'])) {
+               $act = $_GET['playlist'];
+               try {
+                       if($act=="move"&&isset($_GET['from'])&&isset($_GET['to'])) {
+                               // todo: sanity check
+                               $response = null; 
+                               if($pl->moveSongId($_GET['from'], $_GET['to'])) 
+                                       $response = array('result' => "ok");
+                               else $response = array ('result' => PF_FAILURE);
+                               $json = $response;
+                       }
+                       else if($act=="info"&&isset($_POST['ids'])) {
+                               $list = ids_to_list($_POST['ids']);
+                               $ret = array();
+                               foreach($list as $id) {
+                                       $tmp = $pl->getPlaylistInfoId($id);
+                                       if(isset($tmp['file']))
+                                               $ret[] = $tmp['file'][0];
+                               }
+                               $json = array();
+                               unset($list);
+                               $json['result'] = &$ret;
+                       }
+                       else {
+                               $json = array("result" => PF_FAILURE);
+                       }
+               }
+               catch(PEAR_Exception $e) {
+                       $json = array ('result' => PF_FAILURE);
+               }
+       }
+       else if(isset($_GET['rangemove'])&&is_numeric(trim($_GET['rangemove']))&&isset($_GET['elems'])) {
+               $res = PF_FAILURE;
+               $dest = intval($_GET['rangemove']);
+               $pos_offset = 0;
+               try {
+                       $list = selection_to_reverse_list($_GET['elems']);
+                       foreach($list as &$pos) {
+                               //echo $pos-$pos_offset . "=>" .$dest."\n";
+
+                               /* this means we've moved above the place where changing the position will
+                                * have any effect on the list */
+                               if($dest>$pos&&$pos_offset!=0) {
+                                       $pos_offset=0;
+                                       $dest--;
+                               }
+
+                               $pl->moveSong($pos-$pos_offset, $dest);
+                               if($dest>$pos-$pos_offset) {
+                                       /* means we yanked something from over destination */
+                                       $dest--;
+                               }
+                               else if($dest<$pos-$pos_offset) {
+                                       /* means we yanked something from below destination */
+                                       $pos_offset--;
+                               }
+                               else {
+                                       /* moved it to our selves O_o */
+                               //      echo "onself\n";
+                               }
+                       }
+                       $res = "ok";
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array ('result' => $res);
+       }
+       else if(isset($_GET['ping'])) {
+               $result = "pong";
+               $json = array("result" => $result);
+       }
+       else if(isset($_GET['volume'])&&is_numeric(trim($_GET['volume']))) {
+               $res = PF_FAILURE;
+               try {
+                       $volume = trim($_GET['volume']);
+                       $play = get_playback();
+                       if($play->setVolume($volume)) 
+                               $res = "ok";
+                       $play->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['position'])&&is_numeric(trim($_GET['position']))
+               && isset($_GET['id']) && is_numeric(trim($_GET['id']))) {
+               $result = PF_FAILURE;
+               try {
+                       $pos = trim($_GET['position']);
+                       $id = trim($_GET['id']);
+                       $play = get_playback();
+                       if($play->seekId($id, $pos)) 
+                               $result = "ok";
+                       $play->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $result);
+
+       }
+       else if(isset($_GET['currentsong'])) {
+               $res = "failure";
+               try {
+                       $res = $pl->getCurrentSong();
+                       if(!$res) 
+                               $res = "failure";
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['dirlist'])) {
+               $dir = trim($_GET['dirlist']);
+               $FILE = 0;
+               $ARTIST = 1;
+               $ALBUM = 2;
+               $type = $FILE;
+
+               if(isset($_GET['type'])&&is_numeric($_GET['type'])) {
+                       $type = $_GET['type'];
+               }
+               if(is_null($dir)||$dir=="")
+                       $dir = "/";
+               $res = "failure";
+               try {
+                       $db = get_database();
+
+                       if($type==$ALBUM||$type==$ARTIST) {
+                               $t = false;
+                               if(($t = strrpos($dir, "/")) !== false && $t == strlen($dir)-1) {
+                                       $dir = substr($dir, $t+1);
+                               }
+                               if(strlen($dir)==0) {
+                                       $type = $type==$ALBUM?"Album":"Artist";
+                                       $res = array(strtolower($type) => $db->getMetadata($type));
+                               }
+                               else {  
+                                       $res = array();
+                                       if($type==$ALBUM) {
+                                               $res["artist"] = $db->getMetadata("Artist", "Album", $dir);
+                                               $res['filelist'] = $db->find(array("Album" => $dir), true);
+                                       }
+                                       else if($type==$ARTIST) {
+                                               $res["album"] = $db->getMetadata("Album","Artist", $dir);
+                                               $res['filelist'] = $db->find(array("Artist" => $dir), true);
+                                       }
+                               }
+                       }
+                       else {
+                               $tmp = $db->getInfo($dir);
+                               $res = array();
+
+                               if(isset($tmp['directory'])) {
+                                       $res['directory'] =$tmp['directory'];
+                               }
+                               if(isset($tmp['file'])) {
+                                       $res['file'] = array();
+                                       foreach($tmp['file'] as &$row) {
+                                               $res['file'][] = $row['file'];
+                                       }
+                               }
+                               if(isset($tmp['playlist'])) {
+                                       $res['playlist'] = $tmp['playlist'];
+                               }
+                       }
+                       if(!$res) 
+                               $res = "failure";
+                       $db->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['act'])) {
+               $act = trim($_GET['act']);
+               $result = "failure";
+               try {
+                       $play = get_playback(); 
+
+                       if($act=="play") {
+                               if(isset($_GET['id'])&&is_numeric(trim($_GET['id']))) {
+                                       if($play->playId(trim($_GET['id'])))
+                                               $result = "ok";
+                               }
+                               else if(isset($_GET['pos'])&&is_numeric(trim($_GET['pos']))) {
+                                       if($play->play(trim($_GET['pos'])))
+                                               $result = "ok";
+                               }
+                               else if($play->play()) {
+                                       $result = "ok";
+                               }
+                       }
+                       else if($act == "toggle") {
+                               if($play->pause())
+                                       $result = "ok";
+                       }
+                       else if($act == "next") {
+                               if($play->nextSong())
+                                       $result = "ok";
+                       }
+                       else if( $act == "previous") {
+                               if($play->previousSong())
+                                       $result = "ok";
+                       }
+                       else if($act=="stop") {
+                               if($play->stop())
+                                       $result = "ok";
+                       }
+                       else $result = "invalid command";
+                       $play->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+                       $result = "failure";
+               }
+               $json = array("result" => $result);
+       }
+       else if(isset($_GET['add'])) {
+               $add = $_GET['add'];
+               try {
+                       $res = PF_FAILURE;
+                       if($pl->addSong($add)) {
+                               $res = "ok";
+                               $something_added = true;
+                       }
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['remove'])) {
+               $arr = selection_to_reverse_list($_GET['remove']);
+               $res = "ok";
+               try {
+                       foreach($arr as &$val) {
+                               if(!$pl->deleteSong($val))
+                                       $res = "failure";
+                       }
+               }
+               catch(PEAR_Exception $e) {
+                       $result = "failure";
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['updatedb'])) {
+               $res = PF_FAILURE;
+               try {
+                       $adm = get_admin();
+                       if($adm->updateDatabase())
+                               $res = "ok";
+                       $adm->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+                       $res = PF_FAILURE;
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['outputs'])||isset($_GET['output_e'])||isset($_GET['output_d'])) {
+               $res = PF_FAILURE;
+               try {
+                       $admin = get_admin();
+                       if(isset($_GET['outputs']))
+                               $res = $admin->getOutputs();
+                       else if(isset($_GET['output_e'])&&is_numeric($_GET['output_e']))
+                               $res = $admin->enableOutput(trim($_GET['output_e']))?"1":PF_FAILURE;
+                       else if(isset($_GET['output_d'])&&is_numeric($_GET['output_d']))
+                               $res = $admin->disableOutput(trim($_GET['output_d']))?"0":PF_FAILURE;
+                       $admin->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+                       $res = PF_FAILURE;
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['random'])) {
+               $res = "failure";
+               try {
+                       $play = get_playback(); 
+                       $val = $_GET['random']=="1";
+                       if($play->random($val)) {
+                               $res = $val?"1":"0";
+                       }
+                       $play->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+
+       }
+       else if(isset($_GET['repeat'])) {
+               $res = "failure";
+               try {
+                       $play = get_playback(); 
+                       $val = $_GET['repeat']=="1";
+                       if($play->repeat($val)) {
+                               $res = $val?"1":"0";
+                       }
+                       $play->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['xfade'])&&is_numeric($_GET['xfade'])) {
+               $res = PF_FAILURE;
+               try {
+                       $play = get_playback(); 
+                       if($play->setCrossfade(trim($_GET['xfade'])))
+                               $res = "ok";
+                       $play->disconnect();
+                       
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['quick_search'])) {
+               $dir = trim($_GET['quick_search']);
+               $res = PF_FAILURE;
+               try {
+                       $search_dir = strrpos($dir, "/");
+                       if($search_dir) {
+                               $search_dir = substr($dir, 0, $search_dir);
+                       }
+                       else {
+                               $search_dir = "";
+                       }
+                       $db = get_database(); 
+                       $tmp = $db->getInfo($search_dir);
+                       if(isset($tmp['directory'])) {
+                               $res = array();
+                               $i=0;
+                               foreach($tmp['directory'] as $key => &$value) {
+                                       if(stripos($value, $dir)===0) {
+                                               $i++;
+                                               $res[$key] = &$value;
+                                       }
+                                       if($i>=20) /* return up to x entries */
+                                               break;
+                               }
+                       }
+                       $db->disconnect();
+                       
+               }
+               catch(PEAR_Exception $e) {
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['searchadd'])||isset($_GET['searchfile'])) {
+               $artist = null;
+               $album = null;
+               $res = PF_FAILURE;
+               if(isset($_GET['artist'])&&strlen($_GET['artist'])>0)
+                       $artist = $_GET['artist'];
+               if(isset($_GET['album'])&&strlen($_GET['album'])>0)
+                       $album = $_GET['album'];
+               if(!(is_null($artist)&&is_null($album))) {
+               try {
+                       $db = get_database();
+                       $params = array();
+                       if(!is_null($artist)) 
+                               $params["Artist"] = $artist;
+                       if(!is_null($album)) 
+                               $params["Album"] = $album;
+
+
+                       if(isset($_GET['searchadd'])) {
+                               if(search_add($db, $pl, $params)) {
+                                       $res = "ok";
+                                       $something_added = true;
+                               }
+                               else $res = "notfound";
+                       }
+                       else {
+                               $res = array();
+                               $res['filelist'] = $db->find($params, true);
+                       }
+                       $db->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+                       $res = PF_FAILURE;
+               }
+               }
+               $json = array("result" => $res);
+       }
+       else if(((isset($_GET['metasearch'])&&is_numeric($_GET['metasearch']))||
+                (isset($_GET['plsearch'])&&is_numeric($_GET['plsearch'])))
+               &&isset($_GET['s'])) {
+               $plsearch = isset($_GET['plsearch']);
+
+               $type = intval($plsearch?$_GET['plsearch']:$_GET['metasearch']);
+               $search = $_GET['s'];
+               $res = PF_FAILURE;
+               if($type>=0&&$type<count($plsearch?$PL_SEARCH:$META_SEARCH)) {
+                       try {
+                               $tmp = null; 
+                               if($plsearch&&$pl->hasFind()) {
+                                       $tmp = $pl->find(array($PL_SEARCH[$type] => $search));
+                               }
+                               else if($plsearch) {
+                                       $data = $pl->getPlaylistInfoId();
+                                       if(isset($data['file']))
+                                               $data = $data['file'];
+                                       $tmp = array();
+                                       $t = $PL_SEARCH[$type];
+                                       foreach($data as &$song) {
+                                               if($type===0) { // any
+                                                       foreach($song as &$e)
+                                                               if(stristr($e, $search)!==FALSE) {
+                                                                       $tmp[] = $song;
+                                                                       break;
+                                                               }
+                                               }
+                                               else if(isset($song[$t]) && stristr($song[$t],$search)!==FALSE)
+                                                       $tmp[] = $song;
+                                       }
+                               }
+                               else {
+                                       $db = get_database();
+                                       $tmp = $db->find(array($META_SEARCH[$type] => $search));
+                                       $db->disconnect();
+                               }
+                               $res = array();
+                               /* strip */
+                               $keys = array("Artist", "Title", "file", "Pos");
+                               foreach($tmp as &$row) {
+                                       $e = array();
+                                       foreach($row as $key => &$val) {
+                                               if(in_array($key, $keys)!==FALSE)
+                                                       $e[$key] = $val;
+                                       }
+                                       $res[] = $e; 
+                               }
+                       }
+                       catch(PEAR_Exception $e) {
+                               //$res = $e->getMessage();
+                       }
+               }
+               else if($type==count($META_SEARCH)) { // search lyrics...
+                       /* this should probably have been in metadata.php, but don't need 
+                        * to change anything if we have it here, kiss */
+                       $tmp = array();
+                       if(is_dir($metadata_dir)&&is_readable($metadata_dir)) {
+                               $files = scandir($metadata_dir); 
+                               foreach($files as $file) {
+                                       $pos = strrpos($file, ".lyric");
+                                       if($pos!==false&&$pos==strlen($file)-6) {
+                                               $xml = @simplexml_load_file($metadata_dir . $file);
+                                               if($xml&&isset($xml->result[0])&&isset($xml->result[0]->lyric[0])) {
+                                                       $l = (string)$xml->result[0]->lyric[0];
+                                                       if(stripos($l, $search)!==false) {
+                                                               if(isset($xml->file)) {
+                                                                       /*
+                                                                       foreach($xml->file as $f) {
+                                                                               $e = array(); 
+                                                                               $e['file'] = (string)$f;
+                                                                               $e['Artist'] = (string)$xml->result[0]->artist[0];
+                                                                               $e['Title'] = (string)$xml->result[0]->title[0];
+                                                                               $res[] = $e;
+                                                                       }
+                                                                       */
+                                                                       $e = array(); 
+                                                                       $e['Artist'] = (string)$xml->result[0]->artist[0];
+                                                                       $e['Title'] = (string)$xml->result[0]->title[0];
+                                                                       $tmp[] = $e;
+                                                               }
+                                                       }
+                                               }
+                                       }
+                               }
+                       }
+                       $db = get_database();
+
+                       $res = array();
+                       foreach($tmp as &$row) {
+                               $sr = $db->find(array("Artist" => $row['Artist'], "Title" => $row["Title"]));
+                               /*var_dump($tmp);
+                               break;*/
+                               if(isset($sr[0])&&isset($sr[0]['file'])) {
+                                       $row['file'] = $sr[0]['file'];
+                                       $res[] = $row;
+                               }
+                       }
+                       $db->disconnect();
+               }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['ma'])) {
+               /* note to self: should merge single add with this */
+               $res = PF_FAILURE;
+               if (!isset($HTTP_RAW_POST_DATA))
+                  $HTTP_RAW_POST_DATA = file_get_contents("php://input");
+               $ma = explode("\n", $HTTP_RAW_POST_DATA);
+               $db = false;
+               $sparam = array();
+               if(count($ma)) {
+                       $tmp = explode(":", $ma[0], 2);
+                       if($tmp[0]=="baseartist") {
+                               $sparam['Artist'] = $tmp[1];
+                       }
+                       else if($tmp[0]=="basealbum") {
+                               $sparam['Album'] = $tmp[1];
+                       }       
+               }
+               try {
+                       foreach($ma as &$guom) {
+                               $v = explode(":", $guom, 2);
+                               if(!count($v)==2)
+                                       continue;
+                               $res.=$v[0];
+                               if($v[0]=="file"||$v[0]=="directory") {
+                                       $pl->addSong($v[1]);
+                               } /* we should never get same as baseartist/album here, if we do I don't care */
+                               else if($v[0]=="album"||$v[0]=="artist") {
+                                       if($v[0]=="album")
+                                               $sparam["Album"] = $v[1];
+                                       else $sparam["Artist"] = $v[1];
+                                       if(!$db) 
+                                               $db = get_database();
+                                       search_add($db, $pl, $sparam);
+                               }
+                               else if($v[0]=="playlist") {
+                                       $pl->loadPlaylist($v[1]);
+                               }
+                       }
+                       $res = "ok";
+                       $something_added = true;
+                       if($db)
+                               $db->disconnect();
+               }
+               catch(PEAR_Exception $e) { }
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['playlist_rm'])||isset($_GET['playlist_load'])
+               ||isset($_GET['playlist_save'])||isset($_GET['playlist_add_url'])) {
+
+               $res = false;
+               try {
+                       if(isset($_GET['playlist_rm'])) {
+                               $res = $pl->deletePlaylist(trim($_GET['playlist_rm']));
+                       }
+                       else if(isset($_GET['playlist_load'])) {
+                               $res = $pl->loadPlaylist(trim($_GET['playlist_load']));
+                       }
+                       else if(isset($_GET['playlist_save'])) {
+                               $res = $pl->savePlaylist(trim($_GET['playlist_save']));
+                       }
+                       else if(isset($_GET['playlist_add_url'])) {
+                               $url = trim($_GET['playlist_add_url']);
+                               if(stripos($url, "http://")==0) {
+                                       $res = handle_playlist_url($pl, $url);
+                               }
+                       }
+               }
+               catch(PEAR_Exception $e) {}
+               $res = $res?"ok":PF_FAILURE;
+               $json = array("result" => $res);
+       }
+       else if(isset($_GET['clearerror'])) {
+               $pl->clearError();
+               $json = array("result" => "ok");
+       }
+
+       if(!is_null($json)) {
+               try {
+                       if($playlist_empty&&$something_added) {
+                               $play = get_playback();
+                               $play->play();
+                               $play->disconnect();
+                       }
+                       $json["status"] = $pl->getStatus();
+                       if(isset($_GET['plchanges'])&&is_numeric(trim($_GET['plchanges']))&&$_GET['plchanges']!=$json['status']['playlist']) {
+                               $res = $pl->getChanges(trim($_GET['plchanges']), true);
+                               if($res&&isset($res['file'])&&is_array($res['file'])) {
+                                       
+                                       if(isset($_GET['pmax'])&&isset($_GET['page'])) {
+                                               $arr = $res['file'];
+                                               $max = intval($_GET['pmax']);
+                                               $page = intval($_GET['page']);
+                                               $start = $max * $page;
+                                               $end = $start + $max;
+                                               $ret = array();
+                                               foreach($res['file'] as $f) {
+                                                       if($f['cpos']>=$start&&$f['cpos']<$end)
+                                                               $ret[] = $f;
+                                                       if($f['cpos']>=$end)
+                                                               break;
+                                               }
+                                               if(count($ret)>0)
+                                                       $json['plchanges'] = &$ret;
+
+                                       }
+                                       else {
+                                               $json["plchanges"] = &$res['file'];
+                                       }
+                               }
+                       }
+                       $pl->disconnect();
+               }
+               catch(PEAR_Exception $e) {
+               }
+               array_to_json($json);
+       }
+       else {
+               try {
+               $pl->disconnect();
+               } catch(PEAR_Exception $e) {}
+       }
+?>
diff --git a/player/config.php b/player/config.php
new file mode 100644 (file)
index 0000000..da8e021
--- /dev/null
@@ -0,0 +1,374 @@
+<?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.
+*/
+       require_once("../inc/function_test.php");
+       function get_checkbox_from_config($name) {
+               $var = get_config($name);
+               if(!is_null($var)&&strlen($var)) {
+                       return "checked='checked'";
+               }
+               return "";
+       }
+
+       function return_bytes($val) {
+           $val = trim($val);
+           $last = strtolower($val{strlen($val)-1});
+           switch($last) {
+               // The 'G' modifier is available since PHP 5.1.0
+               case 'g':
+                   $val *= 1024;
+               case 'm':
+                   $val *= 1024;
+               case 'k':
+                   $val *= 1024;
+           }
+
+           return $val;
+       }
+
+
+       $title = "";
+       @ob_start();
+       require_once("../inc/base.php");
+       require_once("../lang/master.php");
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html>
+<head>
+<meta name="robots" content="noindex,nofollow" />
+<style type="text/css"> 
+       body {
+               font: 12.4px/160% sans-serif;
+       }
+       .main_container {
+               border: 1px solid #e2e2e2;
+               background-color: #f7f7f7; 
+               padding-left: 8px;
+               padding-bottom: 10px; 
+       }
+       .red-box {
+               border:         1px solid #a20000;
+               background-color: #ffcccc;
+               padding-left: 5px;
+       }
+       #pinfo {
+               border: 1px solid #877E6E; 
+               background-color: #DEE7F7; 
+               background: #f1f1f1;
+               padding: 0px 5px 0px 5px;
+               position: absolute;
+               top: 30px;
+               right: 20px;
+       }
+       select, input {
+               font-size: 0.90em;
+               line-height: 1em;
+       }
+</style>
+<script type='text/javascript'>
+       var showing = false;
+       function toggle_showing_plentries() {
+               var e = null;
+               var i =3; 
+               showing = !showing;
+               while(e = document.getElementById('ple_' + i)) {
+                       e.style.display = showing?"":"none";
+                       i++;
+               }
+               var t = document.getElementById('plentry_show');
+               t.innerHTML = showing?"&nbsp;&nbsp;Hide nonstandard [~]":"&nbsp;&nbsp;Show nonstandard [+]";
+       }
+</script>
+<title><?php echo m("Pitchfork MPD Client Configuration"); ?></title>
+</head>
+<body>
+<?php
+       $new_config = $config?false:true;
+       if(!$config) {
+               $config = simplexml_load_string("<?xml version='1.0' ?>\n<root>\n</root>\n");
+       }
+       if(isset($_POST['submit'])) {
+               $vars = array( 'mpd_host', 'mpd_port', 'mpd_pass', 'login_pass', 'update_delay', 
+                               'metadata_disable', 'theme', 'stop_button', 'shout_url', 'pagination', 'lang');
+               foreach ($vars as $var) {
+                       $add = "";
+                       if(isset($_POST[$var])&&trim($_POST[$var])!="") 
+                               $add = trim($_POST[$var]);
+                       
+                       if($var=="pagination") {
+                               if(!is_numeric($add))
+                                       $add = 0;
+                               else $add = intval($add);
+                       }
+                       else if($var=="login_pass"&&strlen($add)>0) {
+                               if($add== HASH_PASS)
+                                       continue;
+                               $add = generate_hash($add);
+                       }
+
+                       
+                       if(isset($config->$var)) {
+                               $config->$var = $add;
+                       }
+                       else {
+                               $config->addChild($var, $add);
+                       }
+               }
+
+               $plentry = null;
+               if(isset($config->plentry))
+                       $plentry = $config->plentry;
+               else 
+                       $plentry = $config->addChild("plentry");
+
+               foreach($pl_fields as $field) {
+                       $val = isset($_POST['plentry_' . $field])?"yes":"";
+                       $plentry->$field = $val;
+               }
+
+               // need to save config!
+               if($config->asXML("../config/config.xml")) {
+                       header("Location: index.php");
+                       echo "<p>If you're not redirected, go here: <a href='index.php'>player</a></p>";
+                       exit();
+               }
+               else {
+                       echo "<p class='error'>Could not save your configuration, check that config/config.xml is writeable</p>\n";
+               }
+       }
+
+       if(!is_writeable("../config")) {
+               echo "<p class='red-box'>";
+               echo m("Warning: Your config/ directory is not writeable! Please change owner of directory to apache user.");
+               echo "</p>\n";
+       }
+       @ob_end_flush();
+
+?>
+
+
+
+<div class='main_container' id='main_container'>
+
+<h1>Pitchfork configuration</h1>
+
+<?php if(isset($_GET['new_config'])) 
+       echo "<p>" . m("Let us take a minute to configure this player") . "</p>\n";
+   else echo "<p>" . m("Configure settings") . "</p>";
+?>
+<form action="config.php<?php echo $new_config?'?new_config':''; ?>" method="post">
+<h2><?php echo m("Connection-settings"); ?> </h2>
+<p><?php echo m("Where can I find your MPD-server?"); ?></p>
+<table>
+<tr><td><?php echo m("Hostname:"); ?> </td>
+<td><input type='text' value='<?php echo  htmlspecialchars(get_config('mpd_host', 'localhost')) ?>' name='mpd_host' /></td></tr>
+<tr><td><?php echo m("Port:");?>
+</td><td><input type='text' value='<?php echo htmlspecialchars(get_config('mpd_port', '6600')) ?>' name='mpd_port' /></td></tr>
+<tr><td><?php echo m("Password:");?>
+</td><td><input type='password' value='<?php echo htmlspecialchars(get_config('mpd_pass', '')) ?>' name='mpd_pass' /></td></tr>
+
+</table>
+<h2><?php echo m("User interface");?></h2>
+<p><?php echo m("Some other settings!");?><br/></p>
+<table>
+<tr><td><?php echo m("Update time:"); ?> 
+</td><td><input type='text' title='<?php echo m("How often we should request updates from the server");?>' value='<?php echo htmlspecialchars(get_config('update_delay', '1')) ?>' name='update_delay' /></td></tr>
+       <tr><td><?php echo m("Login password (optional):");?>
+       </td><td><input type='password' title='<?php echo m("If you want to require a password to see these pages you may specify it here");?>' value='<?php 
+       
+       $pass = get_config('login_pass', '');
+       if(substr($pass,0, 4)=="sha:") {
+               echo HASH_PASS;
+       }
+       else {
+               echo htmlspecialchars($pass);
+       }
+
+?>' name='login_pass' /></td></tr>
+<tr><td><?php echo m("Theme:");?> </td>
+<td>
+<select name='theme'>
+<?php
+$themes = get_available_themes();
+$ctheme = get_config("theme", "default");
+foreach($themes as $theme) {
+       echo "\n<option value='$theme' ";
+       if($theme==$ctheme)
+               echo "selected='selected' ";
+       echo ">$theme</option>";
+}
+
+?>
+</select>
+</td>
+</tr>
+<tr><td><?php echo m("Language:");?> </td><td>
+<select name="lang">
+<?php 
+       // TODO: move
+       $languages = array("eu" => "Basque", "en" => "English", "fr" => "French", "de" => "German");
+       $clang = get_config("lang", "en");
+       foreach($languages as $l => $n) {
+               echo "\n<option value='$l'";
+               if($l==$clang)
+                       echo " selected='selected' ";
+               echo ">$n</option>";
+       }
+?>
+</select>
+</td></tr>
+
+<tr><td><?php echo m("Include stop button:");?></td><td>
+<input type='checkbox' <?php if(!is_null(get_config("stop_button"))) echo "checked='checked'"; ?> name='stop_button' value='yesplease' />
+</td></tr>
+<tr><td><?php echo m("Pagination:");?></td><td><input name='pagination' type='text' value="<?php echo get_config("pagination", 0); ?>" 
+title="<?php echo m("Maximum number of entries pr. page. Set to 0 to disable.");?>" size="5" /></td></tr>
+<tr><td>&nbsp; </td><td> </td></tr>
+<tr><td colspan="2"><?php echo m("Show these fields in the playlist:");?> </td></tr>
+<tr><td>&nbsp;</td><td><input type='checkbox' disabled='disabled' checked='checked' id='tnode_1' /> <label for='tnode_1'>
+<?php echo m("Position"); ?></label></td></tr>
+<?php
+
+$selected_fields = get_selected_plfields();
+$length = count($pl_fields);
+for($i=0; $i<$length;$i++) {
+       if($i==3) {
+               echo "<tr><td colspan='2' style='cursor: pointer;' id='plentry_show' onclick='toggle_showing_plentries();'>&nbsp;&nbsp;";
+               echo m("Show nonstandard") . " [+]</td></tr>";
+       }
+       echo "<tr id='ple_$i' ";
+       if($i>=3)
+               echo "style='display: none; ' ";
+       echo "><td>&nbsp;</td><td>";
+       echo "<input type='checkbox' ";
+       if($selected_fields[$i])
+               echo "checked='checked' ";
+       echo "name='plentry_".$pl_fields[$i]."' id='pl_i_$i' /> <label for='pl_i_$i'>".$pl_fields[$i]."</label></td></tr>\n";
+}
+
+?>
+<tr><td>&nbsp;</td><td><input type='checkbox' disabled='disabled' checked='checked' id='tnode_2' /> <label for='tnode_2'> Time</label></td></tr>
+</table>
+<h2>Metadata</h2>
+<p><?php echo m("Configuration for retrieving metadata. This requires that the machine pitchfork is running on can access the internet."); ?></p>
+<table>
+<tr><td><?php echo m("Disable metadata:"); ?> </td><td><input type='checkbox' <?php echo get_checkbox_from_config('metadata_disable') ?> name='metadata_disable' /></td></tr>
+</table>
+<h2><?php echo m("Shoutcast integration"); ?></h2>
+<p>
+<?php echo m("Optionally specify the URL to the shout stream provided by mpd to enable integration with pitchfork.");?> <br/>
+<input size="35" type='text' name='shout_url' value='<?php if(!is_null(get_config("shout_url"))) echo htmlspecialchars(get_config("shout_url")); ?>' />
+</p>
+<p style='padding: 12px 0px 12px 00px;'>
+<input name='cancel' type='button' value='Cancel' onclick='window.location = "index.php" ' />
+<input name='submit' type="submit" value="Save" />
+</p>
+</form>
+<?php if(!isset($_GET['new_config'])) { ?>
+<hr/>
+<p>
+For lyrics search to work in the directory browser file-names has to be saved with the lyrics, however when you move/delete files from your library this file reference become wrong. This button removes any references to such files.
+<br/>
+<span id='housecleaning_info'></span>
+<input type='button' value='Housecleaning' onclick='location.href="metadata.php?housecleaning"'/>
+</p>
+<?php } 
+
+function print_yesno($test, $fatal) {
+       if($test) return "<span style='color: green;'>" . m("Yes") . "</span>";
+       else return "<span style='color: " . ($fatal?"red":"orange") . ";'>" . m("No") . "</span>";
+}
+
+// function_name:fatal (0/1)
+function test_function($stuff) {
+       $stuff = explode(":", $stuff);
+       $name = $stuff[0];
+       echo $name . ": ";
+       echo print_yesno(function_exists($name), $stuff[1]);
+       echo "<br/>\n";
+}
+
+?>
+
+<div id='pinfo'>
+<h2><?php echo m("Pitchfork info"); ?></h2>
+<p style='padding: 0px 0px 4px 0px;'>
+<?php 
+       echo m("Release version:") . " $release_version<br/>\n";
+       echo m("Release date:") . " $release_date<br/><br/>\n";
+       $pl = get_playback();
+       $has_commands = true;
+       try {
+               if($pl) {
+                       $commands = $pl->getCommands();
+                       /* these are just some of the needed commands */
+                       $needed = array("outputs", "disableoutput", "enableoutput", "plchangesposid");
+                       $res = array_intersect($needed, $commands);
+                       if(count($res)!=count($needed))
+                               $has_commands = false;
+                       $pl->disconnect();
+               }
+       }
+       catch(PEAR_Exception $e) {
+               $has_commands = false;
+       }
+
+       echo m("Connect to mpd:"). " ". print_yesno($pl, true) . "<br/>\n";
+       if($pl) {
+               echo m("MPD commands:")." " . print_yesno($has_commands, true) . "<br />\n";
+       }
+       echo m("Metadata directory:"). " " . print_yesno((file_exists($metadata_dir)&&is_writeable($metadata_dir))
+                               ||(!file_exists($metadata_dir)&&is_writeable($config_dir)), true);
+?>
+</p>
+
+<h3><?php echo m("Functions:"); ?></h3>
+<p style='padding: 0px 0px 4px 0px; '>
+<?php 
+       // name:fatal
+       foreach(array("json_encode:0", "simplexml_load_string:1", "mb_internal_encoding:0") as $f)
+               test_function($f);
+       echo "SimpleXMLaddChild: ";
+       $sxe = array_to_xml(array("test"));
+       if($sxe)
+               echo print_yesno(is_callable(array($sxe, "addChild"), true), true) . "<br/>";
+       else echo "<span class='color: red'>error</span>\n";
+       $mem = ceil(return_bytes(ini_get("memory_limit"))/(1024*1024));
+       echo m("PHP memory limit:") . " <span style='color: " . ($mem<32?"orange":"green") . "'>" . $mem . "MB</span>";
+
+?>
+</p>
+<?php
+       if(get_magic_quotes_runtime()) {
+               echo "<p style='color: orange'>";
+               echo m("Warning: Magic quotes runtime is on, <br/>please use pitchfork.conf or turn<br/> of manually.");
+               echo "</p>\n";
+       }
+       if(get_magic_quotes_gpc()) {
+               echo "<p style='color: orange'>";
+               echo m("Warning: Magic quotes gpc is on, <br/>please use pitchfork.conf or turn<br/> of manually.");
+               echo "</p>\n";
+       }
+
+?>
+</div>
+
+</div>
+
+</body>
+</html>
diff --git a/player/index.php b/player/index.php
new file mode 100644 (file)
index 0000000..56588e7
--- /dev/null
@@ -0,0 +1,135 @@
+<?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.
+*/
+
+       include_once("../inc/function_test.php");
+       require_once('../inc/base.php');
+       require_once("../lang/master.php");
+       header("Content-Type: text/html; charset=UTF-8");
+       header("Expires: Mon, 26 Jul 1997 05:00:00 GMT"); // Date in the past
+       header("Last-Modified: " . gmdate("D, d M Y H:i:s") . " GMT"); // always modified
+       header("Cache-Control: no-store, no-cache, must-revalidate, max-age=0"); // HTTP/1.1
+       header("Cache-Control: post-check=0, pre-check=0", false);
+       header("Pragma: no-cache"); // HTTP/1.0
+?>
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+
+<html>
+       <head>
+               <link rel="stylesheet" type="text/css" href="../std/base.css" /> 
+               <link rel="stylesheet" type="text/css" href="../theme/<?php echo htmlentities($selected_theme); ?>/theme.css" /> 
+<?php 
+       $scripts = array("player/preferences.js.php", "lang/en.js", "std/collection.js", "std/toolkit.js", "std/streaming.js",
+               "std/plsearch.js", "std/playlist.js", "std/keyboard.js", "std/browser.js", "std/quickadd.js", 
+               "std/command.js",  "theme/" . htmlentities($selected_theme) . "/theme.js" );
+               if($language != "en")
+                       $scripts[] = "lang/".$language.".js";
+
+               if(is_null(get_config("metadata_disable")))
+                       $scripts[] = "std/metadata.js";
+
+               foreach($scripts as $script)
+                       echo "\t\t<script type=\"text/JavaScript\" src=\"../".$script."\"></script>\n";
+               ?>
+               <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
+               <meta name="robots" content="noindex,nofollow" />
+               <title>Pitchfork MPD Client</title>
+       </head>
+<body onload='init_player()'>
+
+<div class='player_control' style='' id='player_control'>
+       
+       <div id='status_bar'>
+               <p id='status_bar_txt'></p>
+               <img id='status_bar_img' class='status_working' />
+       </div>
+
+       <div id='albumart'></div>
+       <div class='pc_artist' >
+               <p class='disp'><span id='disp_title'></span><br/></p>
+               <p class='disp' ><span id='disp_artist'></span><br/></p>
+               <p class='disp'><span id='disp_album'></span><br/></p>
+       </div>
+
+       <div class='pc_ci' >
+               <div class='nomargin'>
+               <img id='previous_button' class='act_button fakelink'/>
+               <img id='stop_button' style='display: none; ' class='act_button fakelink' />
+               <img id='pp_button' class='act_button fakelink' />
+               <img id='next_button' class='act_button fakelink'/>
+               </div>
+               <p class='disp'><span id="disp_info"></span></p>
+               <input type='text' id='quickadd' value='<?php echo m("Quick add"); ?>' />
+               <div id='qa_suggestions' ><p id='qa_suggestions_txt' ></p></div>
+       </div>
+
+       <div class='pc_sliders'>
+               <div id='posslider' ></div>
+               <div id="volslider" ></div> 
+       </div>
+       
+       <div class='pc_settings'>
+       <div id='settings_header' class='settings_header'><p class='nomargin' style='padding-left: 10px;'><?php echo m("Server settings"); ?></p></div>
+               <div class='settings_container' id='settings_container'><p id='settings_content'></p></div>
+       </div>
+
+       <div class='pc_other'><ul class='nomargin'>
+               <li class='menuitem fakelink' title="<?php echo m("Add playlists or audio-files"); ?>" 
+                               id='playlist_add'><?php echo m("Add playlist"); ?></li>
+               <li class='menuitem fakelink' title="<?php echo m("Save current playlist"); ?>" 
+                               id='playlist_save'><?php echo m("Save playlist"); ?></li>
+               <li class='menuitem fakelink' title='<?php echo m("Search current playlist"); ?>' 
+                               id='playlist_search_btn' ><?php echo m("Search playlist"); ?></li>
+               <li class='menuitem' ><a class='pc_other' href='config.php'><?php echo m("Configure"); ?></a></li>
+
+               <?php 
+               if(!is_null(get_config("shout_url"))) 
+                       echo "\t\t<li class='menuitem fakelink' title='" . m("Start streaming to browser") . "' id='streaming_open'>". 
+                               m("Streaming") . "</li>\n";
+               if(is_null(get_config('metadata_disable'))) { 
+                       echo "\t\t<li title='" . m("Get music recommendations based on current playlist") . "' id='recommendation_open' ".
+                               "class='menuitem fakelink'>".  m("Recommendation") . "</li>\n";
+                       echo "\t\t<li title='" . m("Get more information about this song/album") . "' id='metadata_open' class='menuitem fakelink'>".
+                               m("Song Info") . "</li>\n";
+
+               }
+               ?>
+       </ul></div>
+</div>
+
+<div class='selection_menu'>
+<img id='crop_items_button' class='menu_button fakelink' title="<?php echo m("Crop to selection"); ?>" />
+<img id='remove_items_button' class='menu_button fakelink' title="<?php echo m("Remove selection"); ?>" />
+<img id='open_directory_button' class='menu_button fakelink' title="<?php echo m("Open directory"); ?>" />
+</div>
+
+<div id='content'>
+<table id='playlist' ></table>
+</div>
+
+<div id="sidebar_header"><p class='nomargin'><span class='fakelink' id='metadata_open_lyrics'>[<?php echo m("Lyrics"); ?>]</span> <span id='metadata_open_description' class='fakelink'>[<?php echo m("Album description"); ?>]</span> <span id='metadata_open_review' class='fakelink'>[<?php echo m("Album review"); ?>]</span> <span id='metadata_close' class='fakelink'>[<?php echo m("Close"); ?>]</span></p></div>
+<div id='sidebar_display'><p class='nomargin' id='sidebar_display_txt'> </p></div>
+
+<div id='pagination_options'><img class="pagination_options" src='' id='pagination_jump_current'/><img class="pagination_options" src='' id='pagination_follow_current'/></div>
+<?php if(get_config("pagination", "0")!="0") {?>
+<div id='pagination'><ul id='pagination_list'></ul></div>
+<div id='pagination_spacer'> </div>
+<?php } ?>
+
+</body>
+</html>
diff --git a/player/login.php b/player/login.php
new file mode 100644 (file)
index 0000000..4ad58e2
--- /dev/null
@@ -0,0 +1,91 @@
+<?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.
+*/
+       $error = false;
+       $no_require_login = "true";
+       require_once("../inc/base.php");
+       if(isset($_POST['password'])) {
+               $pass = get_config("login_pass");
+               if(substr($pass,0, 4)=="sha:") {
+                       if(check_hash($pass, trim($_POST['password']))) {
+                               $_SESSION['logged_in'] = true;
+                               header("Location: index.php");
+                               exit(); 
+                       }
+                       $error = "Login failed";
+               }
+               else if($pass==trim($_POST['password'])) {
+                       $_SESSION['logged_in'] = true;
+                       header("Location: index.php");
+                       exit(); 
+               }
+               else {
+                       $error = "Login failed";
+               }
+       }
+       else if(isset($_GET['logout'])) {
+               session_destroy();
+               header("Location: login.php");
+               exit();
+       }
+?>
+<html>
+<head>
+<title>Pitchfork login</title>
+<meta name="robots" content="noindex,nofollow" />
+<style type="text/css"> 
+       body {
+               text-align: center;
+       }
+       h1 {
+               font-size: 18px; 
+       }
+       div.container {
+               display:        block;
+               overflow:       visible;
+               padding:        10px 25px 10px 25px;
+               width:          500px;
+               margin:         0 auto;
+               border:         1px solid #B0BDEC; 
+               background-color: #DEE7F7; 
+       }
+       p.error {
+               border:         1px solid #a20000;
+               background-color: #ffcccc;
+               padding: 5px;
+       }
+</style>
+</head>
+<body onload="document.getElementById('password').focus();">
+<div class='container'>
+<h1>Pitchfork login</h1>
+<?php
+       if($error) {
+               echo "<p class='error'>$error</p>";
+       }
+       if(isset($_SESSION['logged_in'])&&$_SESSION['logged_in']) {
+               echo "<p>Already logged in. <a href='login.php?logout'>Log out?</a></p>\n";
+       }
+?>
+       <form method="post" action="login.php">
+               Password: <input type='password' id="password" name='password' />
+               <input type='submit' name='submit' value='Log in'/>
+       </form>
+</div>
+</body>
+</html>
diff --git a/player/metadata.php b/player/metadata.php
new file mode 100644 (file)
index 0000000..5da7607
--- /dev/null
@@ -0,0 +1,787 @@
+<?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.
+*/
+       ob_start();
+       
+       /* how much time must pass before we try searching for cover art again */
+       $COVER_SEARCH_AGAIN = 86400;
+
+       $amazon_base_url = "http://webservices.amazon.com/onca/xml?Service=AWSECommerceService&SubscriptionId="
+                   . "15BH771NY941TX2NKC02";
+       $amazon_review_url = $amazon_base_url . "&ResponseGroup=EditorialReview&Operation=";
+       require_once("../inc/base.php");
+       require_once("metadata_cover.php");
+       
+       /* metadata should not require locking of session */
+       session_write_close();
+
+       $missing_metadata_dir = false;
+
+       if(!file_exists($metadata_dir)) {
+               if(!mkdir($metadata_dir, 0755)) {
+                       $missing_metadata_dir = true;
+               }
+       }
+       if(!is_writeable($metadata_dir)) {
+               $missing_metadata_dir = true;
+       }
+
+       if($missing_metadata_dir) {
+               $xml = array_to_xml(array("result" => "nocachedir"));
+               echo $xml->asXML();
+               exit();
+       }
+
+       function escape_name($name) {
+               return str_replace(DIRECTORY_SEPARATOR, "_", $name);
+       }
+
+       function get_cover_base_name($artist, $album) {
+               global $cover_dir;
+               return $cover_dir . escape_name($artist) . " - " . escape_name($album);
+       }
+
+       function get_album_info_name($artist, $album) {
+               return get_cover_base_name($artist, $album) . ".txt";
+       }
+       function get_lyric_filename($artist, $title) {
+               global $cover_dir; 
+               return $cover_dir . escape_name($artist) . " - ". escape_name($title) . ".lyric";
+       }
+
+       function find_lyrics($arr) {
+               foreach($arr as $val) {
+                       if(!is_array($val))
+                               continue;
+                       if(isset($val['name'])&&$val['name']=="RETURN") {
+                               return $val['children'];
+                       }
+                       else if(is_array($val)) {
+                               $ret = find_lyrics($val);
+                               if($ret)
+                                       return $ret;
+                       }
+               }
+               return false;
+       }
+
+       function fp_get_contents($fp) {
+               $ret = "";
+               $tmp = false;
+               while($tmp = fgets($fp)) 
+                       $ret .= $tmp;
+               fseek($fp, 0);
+               if(strlen($ret)==0)
+                       return false;
+               return $ret;
+       }
+
+       /* Queries amazon with the specified url, strict serach first and then a more careless one, 
+        * will urlencode artist and albumname 
+        * returns xml document or false upon failure */
+       function amazon_album_query($base_url, $artist, $album) {
+               $stype = array("Title", "Keywords");
+               $artist = urlencode($artist);
+               $album = urlencode($album);
+               foreach($stype as $st) {
+                       if(!amazon_wait())
+                               return false;
+                       $xml = @simplexml_load_string(@file_get_contents($base_url . "&Artist=$artist&$st=$album"));
+                       if($xml&&isset($xml->Items[0])&&isset($xml->Items[0]->Item[0]))
+                               return $xml;
+               }
+               return false;
+       }
+
+       /* returns file pointer or false */
+       function get_album_lock($artist, $album) {
+               $file_name = get_album_info_name($artist, $album);
+               $exists = file_exists($file_name);
+               $fp = false;
+
+               if($exists) 
+                       $fp = @fopen($file_name, "r+");
+               else $fp = @fopen($file_name, "w+");
+               if($fp && flock($fp, LOCK_EX))
+                       return $fp;
+
+               trigger_error("Can't lock album-file: $file_name", E_USER_WARNING);
+               return false;
+       }
+
+       /* waits for appropriate amazon time, have to be called before making any amazon requests
+          returns true if ok to continue otherwise false */
+       function amazon_wait() {
+               global $metadata_dir;
+               
+               /* rationale behind this: 
+                * amazon requires that we don't make more than one request pr. second pr. ip */
+               
+               $file_name = $metadata_dir . "amazon_time";
+               if(file_exists($file_name)) 
+                       $fp = @fopen($file_name, "r+");
+               else $fp = @fopen($file_name, "w+");
+
+               if(!$fp) {
+                       trigger_error("Can't open amazon_time", E_USER_WARNING);
+                       return false; 
+               }
+               if(!flock($fp, LOCK_EX)) {
+                       @fclose($fp);
+                       trigger_error("Can't lock amazon_time", E_USER_WARNING);
+                       return false;
+               }
+
+               $last = fp_get_contents($fp);
+               if($last) {
+                       $stime = 1000;
+                       if(is_numeric($last)) {
+                               $stime = current_time_millis() - $last;
+                       }
+                       $stime = abs($stime);
+                       if($stime<1000)
+                               usleep($stime*1000); // micro seconds
+               }
+
+               if(@fwrite($fp, current_time_millis())===false) {
+                       @fclose($fp);
+                       trigger_error("Can't write to amazon_time", E_USER_WARNING);
+                       return false; 
+               }
+               else {
+                       @fclose($fp);
+                       return true;
+               }
+       }
+
+       /* returns artist and album info and get's album lock or dies */
+       /* return value: array($fp, $artist, $album) */
+       function init_album_artist_or_die() {
+               ob_end_clean();
+               header("Content-Type: text/xml; charset=UTF-8");
+
+               $album = "";
+               $artist = "";
+               if(isset($_GET['artist'])&&isset($_GET['album']) &&
+                  strlen(trim($_GET['artist']))>0&&strlen(trim($_GET['album']))>0) {
+                       $album = trim($_GET['album']);
+                       $artist = trim($_GET['artist']);
+               }
+               else {
+                       $xml = array_to_xml(array("result" => "missingparam"));
+                       echo $xml->asXML();
+                       exit();
+               }
+
+               $fp = get_album_lock($artist, $album);
+
+               if(!$fp) {
+                       $xml = array_to_xml(array("result" => "failed"));
+                       echo $xml->asXML();
+                       exit();
+               }
+               return array($fp, $artist, $album);
+       }
+
+       /* returns array(artist, album, filename) or false */
+       function get_current_info() {
+               try {
+                       $pl = get_playback();
+                       if($pl) {
+                               $info = $pl->getCurrentSong();
+                               if(isset($info['Artist'])&&isset($info['Title'])) {
+                                       $artist = trim($info['Artist']);
+                                       $title = trim($info['Title']);
+                                       $file_name = $info['file'];
+                                       return array($artist, $title, $file_name);
+                               }
+                       }
+                       $pl->disconnect();
+               }
+               catch(PEARException $e) {
+               }
+               return false;
+       }
+
+
+       function get_cover() {
+               global $COVER_SEARCH_AGAIN, $amazon_base_url,$cover_providers;
+
+               list($fp, $artist, $album) = init_album_artist_or_die();
+
+               $xml = fp_get_contents($fp);
+               if($xml) {
+                       $xml = @simplexml_load_string($xml);
+                       if($xml) {
+                               $use_cache = true;
+                               if(isset($xml->notfound)&&is_numeric((string)$xml->notfound[0])) {
+                                       $time = @intval((string)$xml->notfound[0]);
+                                       if($time+$COVER_SEARCH_AGAIN<time())
+                                               $use_cache = false;
+                               }
+                               else if(!isset($xml->image[0])&&!isset($xml->thumbnail[0])) {
+                                       $use_cache = false;
+                               }
+
+                               if($use_cache) {
+                                       $xml->addChild("cached", "true");
+                                       echo $xml->asXML();
+                                       exit();
+                               }
+                       }
+               }
+
+
+               $res = false;
+
+               foreach($cover_providers as $cp) {
+                       $res = $cp($artist, $album);
+                       if($res&&is_array($res))
+                               break;
+               }
+
+               if($xml) {
+                       if($res&&is_array($res)) {
+                               foreach($res as $key => $val) {
+                                       if(!isset($xml->$key))
+                                               $xml->$key = (string)$val;
+                               }
+                       }
+                       else {
+                               $xml->notfound = time();
+                       }
+               }
+               else {
+                       if($res&&is_array($res)) {
+                               $res['time'] = time();
+                               $xml = array_to_xml($res);
+                       }
+                       else {
+                               $xml = array("notfound" => time());
+                               $xml = array_to_xml($xml);
+                       }
+               }
+
+               @fwrite($fp, $xml->asXML());
+
+               @fclose($fp);
+               echo $xml->asXML();
+               exit();
+       }
+
+       function get_review() {
+               global $amazon_review_url, $COVER_SEARCH_AGAIN;
+
+               list($fp, $artist, $album) = init_album_artist_or_die();
+
+               $xml = fp_get_contents($fp);
+               $asin = "";
+               $desc = false; 
+               $review = false;
+               $review_src = false;
+               $no_search = false; 
+               $failed = false; 
+               $changed = false; 
+
+
+               if($xml) {
+                       $xml = @simplexml_load_string($xml);
+                       if($xml) {
+                               if(isset($xml->rnotfound)&&is_numeric((string)$xml->rnotfound[0])) {
+                                       $time = @intval((string)$xml->rnotfound[0]);
+                                       if($time+$COVER_SEARCH_AGAIN>time())
+                                               $no_search = true;
+                               }
+                       }
+               }
+
+               if(!$xml||(!(isset($xml->review[0])||isset($xml->desc[0]))&&!$no_search)) {
+                       $res = false;
+                       if(!amazon_wait()) {
+                               echo array_to_xml(array("result" => "failed"))->asXML();
+                               exit();
+                       }
+
+                       if($xml&&isset($xml->asin[0])) {
+                               $res = @file_get_contents($amazon_review_url . "ItemLookup&IdType=ASIN&ItemId=" . urlencode($xml->asin[0]));
+                               if($res)
+                                       $res = @simplexml_load_string($res);
+                               $asin = false;
+                       }
+                       else {
+                               $res = @amazon_album_query($amazon_review_url . "ItemSearch&SearchIndex=Music&Artist=" , $artist , $album);
+                       }
+                       if($res) {
+                               if($res&&isset($res->Items[0])&&isset($res->Items[0]->Item[0])) {
+                                       $p = $res->Items[0]->Item[0];
+                                       $asin = (string) $p->ASIN;
+                                       if(isset($p->EditorialReviews[0])) {
+                                               $p = $p->EditorialReviews[0];
+                                               foreach($p->EditorialReview as $er) {
+                                                       if(!$desc&&"Album Description" == (string)$er->Source) {
+                                                               $desc = (string) $er->Content;
+                                                       }
+                                                       else if(!$review) {
+                                                               $review_src = (string) $er->Source;
+                                                               $review = (string) $er->Content;
+                                                       }
+                                               }
+                                       }
+                                       /* set info in xml-file... */
+                                       if($xml) {
+                                               if($review) {
+                                                       $xml->review_src = htmlspecialchars($review_src); 
+                                                       $xml->review = htmlspecialchars($review);
+                                               }
+                                               if($desc) {
+                                                       $xml->desc = htmlspecialchars($desc);
+                                               }
+                                               if(!isset($xml->asin[0])) {
+                                                       $xml->addChild("asin", $asin);
+                                                       $changed = true;
+                                               }
+                                               if(!$review&&!$desc) {
+                                                       $failed = true;
+                                               }
+                                               else {
+                                                       $changed = true;
+                                               }
+                                       }
+                                       else {
+                                               $xml = array();
+                                               $xml['asin'] = $asin;
+                                               if($desc) 
+                                                       $xml['desc'] = $desc;
+                                               if($review) {
+                                                       $xml['review_src'] = $review_src;
+                                                       $xml['review'] = $review;
+                                               }
+                                               if(!$review&&!$desc)
+                                                       $failed = true; 
+                                               $xml = array_to_xml($xml);
+                                               $changed = true;
+                                       }
+                               }
+                               else {
+                                       $failed = true; 
+                               }
+                       }
+                       else {
+                               $failed = true;
+                       }
+               }
+               else {
+                       $xml->addChild("cached", "true");
+               }
+
+               if($xml) {
+                       if($failed) {
+                               if(isset($xml->rnotfound)) {
+                                       $xml->rnotfound = time();
+                               }
+                               else {
+                                       $xml->addChild("rnotfound", time());
+                               }
+                               @fwrite($fp, $xml->asXML());
+                       }
+                       else if($changed) {
+                               @fwrite($fp, $xml->asXML());
+                       }
+               }
+               else {
+                       $xml = array_to_xml(array("rnotfound" => time()));
+                       @fwrite($fp, $xml->asXML());
+               }
+               @fclose($fp);
+               echo $xml->asXML();
+               exit();
+       }
+
+       /* artist, title and song file name in system */
+       function _get_lyric_lyricwiki($artist, $title, $file_name) {
+               $file = get_lyric_filename($artist, $title);
+               $fp = fsockopen("lyricwiki.org", 80);
+               if(!$fp) {
+                       $xml = array_to_xml(array("result"=>"connectionfailed"));
+                       return $xml->asXML();
+               }
+               
+               $out = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n";
+               $out .= "<SOAP-ENV:Envelope SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\" ";
+               $out .= "xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" ";
+               $out .= "xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" ";
+               $out .= "xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ";
+               $out .= "xmlns:SOAP-ENC=\"http://schemas.xmlsoap.org/soap/encoding/\" ";
+               $out .= "xmlns:tns=\"urn:LyricWiki\">";
+               $out .= "<SOAP-ENV:Body><tns:getSong xmlns:tns=\"urn:LyricWiki\">";
+               $out .= "<artist xsi:type=\"xsd:string\">";
+               $out .= htmlspecialchars($artist);
+               $out .= "</artist>";
+               $out .= "<song xsi:type=\"xsd:string\">";
+               $out .= htmlspecialchars($title);
+               $out .= "</song>";
+               $out .= "</tns:getSong></SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n";
+
+               $head = "POST /server.php HTTP/1.1\r\n";
+               $head .= "Host: lyricwiki.org\r\n";
+               $head .= "SOAPAction: urn:LyricWiki#getSong\r\n";
+               $head .= "Content-Type: text/xml; charset=UTF-8\r\n";
+               $head .= "User-Agent: RemissPitchfork/0.1\r\n";
+               $head .= "Content-Length: " . str_byte_count($out) . "\r\n";
+               $head .= "Connection: Close\r\n\r\n";
+
+               fwrite($fp, $head . $out);
+
+               $responseHeader = "";
+               /* assume everything is dandy */
+               do {
+                       $responseHeader.= fread($fp, 1);
+               }
+               while (!preg_match('/\\r\\n\\r\\n$/', $responseHeader));
+
+               $ret = "";
+               while(!feof($fp)) {
+                       $ret .= fgets($fp, 128);
+               }
+               fclose($fp);
+               /* stupid hack to get around wrong xml declearation */
+               $qmark = "?";
+               if(strpos($ret, "<". $qmark . "xml version=\"1.0\" encoding=\"ISO-8859-1\"".$qmark.">")===0)
+                       $ret = str_replace("<". $qmark . "xml version=\"1.0\" encoding=\"ISO-8859-1\"".$qmark.">",
+                                       "<". $qmark . "xml version=\"1.0\" encoding=\"UTF-8\"".$qmark.">",
+                                       $ret);
+
+               /*echo $ret;
+               exit();*/
+               $parser = new xml2array();
+               $parser->parse($ret);
+               $data = find_lyrics($parser->arrOutput);
+               // check that data is ok and lyrics exist
+               if($data&&isset($data[2]['tagData'])) {
+                       $res = array();
+                       foreach($data as $d) {
+                               if($d['name']=="ARTIST") 
+                                       $res['artist'] = $d['tagData'];
+                               else if($d['name']=="SONG") 
+                                       $res['title'] = $d['tagData'];
+                               else if($d['name']=="LYRICS")
+                                       $res['lyric'] = $d['tagData'];
+                               else if($d['name']=="URL")
+                                       $res['url'] = $d['tagData'];
+                       }
+                       $res['from'] = "lyricwiki.org";
+                       $res['time'] = time();
+                       /* this caching thing will have to be extracted if we 
+                        * put in another lyrics source */
+                       if(trim($res['lyric'])&&trim($res['lyric'])!="Not found") {
+                               $xml = array_to_xml(array("result" => $res));
+                               $xml->addChild("file", htmlspecialchars($file_name));
+                               $res = $xml->asXML();
+                               @file_put_contents($file, $res);
+                       }
+                       else {
+                               $out = array("result" => "notfound");
+                               if(isset($res['url']))
+                                       $out['url'] = $res['url'];
+                               $res = array_to_xml($out);
+                               $res = $res->asXML();
+                       }
+                       return $res;
+               }
+               return false;
+       }
+
+       /* $file: filename of cached version
+        * $file_name: file name of song */
+       function _get_lyric_cache($file, $file_name) {
+               $xml = @simplexml_load_file($file);
+               if($xml) {
+                       $add_file = true;
+                       if(isset($xml->file)) {
+                               foreach($xml->file as $f) {
+                                       if(((string)$f)==$file_name)
+                                               $add_file = false;
+                               }
+                       }
+                       if($add_file) {
+                               $xml->addChild("file", htmlspecialchars($file_name));
+                               @file_put_contents($file, $xml->asXML());
+                       }
+                       $xml->addChild("cached", "true");
+                       return $xml->asXML();
+               }
+               return false;
+       }
+
+       function get_lyric($info = null) {
+               header("Content-Type: text/xml; charset=UTF-8");
+               ob_end_clean();
+               if(is_null($info))
+                       $info = get_current_info();
+               if(!$info) {
+                       $xml = array_to_xml(array("result"=>"failed"));
+                       echo $xml->asXML();
+                       exit();
+               }
+               $artist = $info[0];
+               $title = $info[1];
+               $file_name = $info[2];
+
+               $file = get_lyric_filename($artist, $title);
+               if(file_exists($file)&&!isset($_GET['force'])) {
+                       $xml = _get_lyric_cache($file, $file_name);
+                       if($xml) {
+                               echo $xml;
+                               exit();
+                       }
+               }
+
+               $xml = _get_lyric_lyricwiki($artist, $title, $file_name);
+               if($xml) {
+                       echo $xml;
+               }
+               else {
+                       echo array_to_xml(array("result" => "failed"))->asXML();
+               }
+               exit();
+       }
+
+       function get_pic() {
+
+               global $cover_dir;
+               $b_name = basename(trim($_GET['pic']));
+               $name = $cover_dir . $b_name;
+               if(file_exists($name)&&is_readable($name)) {
+                       if(function_exists("finfo_open")&&function_exists("finfo_file")) {
+                               $f = finfo_open(FILEINFO_MIME); 
+                               header("Content-Type: " . finfo_file($f, $name));
+                       }
+                       else if(function_exists("mime_content_type")) {
+                               header("Content-Type: " . mime_content_type($name));
+                       }
+                       else {
+                               header("Content-Type: image/jpeg");
+                       }
+                       $c = "Content-Disposition: inline; filename=\"";
+                       $c .= rawurlencode($b_name) . "\"";
+                       header($c);
+                       echo @file_get_contents($name);
+                       ob_end_flush();
+                       exit();
+               }
+               else {
+                       echo "File does not exist\n";
+                       trigger_error("Did not find albumart althought it was requested", E_USER_WARNING);
+                       exit(); 
+               }
+       }
+
+       function get_recommendations_from_playlist() {
+               require_once("../player/openstrands.php");
+               $pl = get_playlist();
+               $list = $pl->getPlaylistInfo();
+               $artist = array();
+               foreach($list as $song) {
+                       if(isset($song['Artist'])&&$song['Artist'])
+                               $artist[$song['Artist']] = true;
+               }
+               $artist = array_keys(array_change_key_case($artist));
+               $pl->disconnect();
+
+               header("Content-Type: text/xml; charset=UTF-8");
+               
+               $ret = strands_get_recommendations($artist);
+               $res = array();
+               if(!$ret || ! count($ret)) {
+                       $res['result'] = is_array($ret)?"notfound":"failed";
+                       echo array_to_xml($res)->asXML();
+                       exit();
+               }
+               $db = get_database();
+               foreach($ret as $a) {
+                       $tmp = array();
+                       $tmp['name'] = $a;
+                       $tmp['album'] = $db->getMetadata("Album", "Artist", $a);
+                       $res[] = $tmp;
+               }
+               $out = array("result" => $res);
+               $db->disconnect();
+               echo array_to_xml($out)->asXML();
+       }
+
+       function do_houseclean() {
+               /* this is a *very* inefficient method, but it's needed... */
+               //header("Content-Type: text/xml; charset=UTF-8");
+               header("Content-type: multipart/x-mixed-replace;boundary=--ThisRandomString");
+               
+               global $metadata_dir;
+               
+               echo "--ThisRandomString\n";
+               $out = "Content-type: text/html\n\n".
+                       "<html><head><title>Housecleaning</title></head><body>\n".
+                       "<p>Performing housecleaning, please wait...</p>\n";
+
+               echo "$out--ThisRandomString\n";
+               ob_end_flush();
+               flush();
+               set_time_limit(300); // this might take a while, but 
+                                   // shouldn't be more than 5 mins even on slow machines
+               $db = get_database();
+               $res = "failed";
+               try {
+                       $time = current_time_millis();
+                       $list = $db->getAll();
+                       if(!isset($list['file']))
+                               return;
+                       $files = $list['file'];
+                       $db->disconnect(); 
+                       $list = scandir($metadata_dir);
+                       $total = count($list);
+                       $fixed = 0;
+                       $errors = 0;
+                       $fcount = 0;
+                       $fcount_inv = 0;
+                       $tcount = 0;
+                       foreach($list as $f) {
+                               $r = strrpos($f, ".lyric");
+                               $tcount++;
+                               if($r!==false&&$r+6==strlen($f)) {
+                                       $xml = @simplexml_load_file($metadata_dir . $f);
+                                       $fcount++;
+                                       if($fcount%100 == 0) {
+                                               echo $out;      
+                                               echo "<p>Processed $fcount (".(int)($tcount*100/$total)."%)..</p>\n";
+                                               echo "--ThisRandomString\n";
+                                               flush();
+
+                                       }
+                                       if($xml) {
+                                               $x_files = array();
+                                               foreach($xml->file as $v) {
+                                                       $x_files[] = (string)$v;
+                                               }
+                                               $dis = array_intersect($x_files, $files);
+                                               if(count($dis)!=count($x_files)) {
+                                                       $dom = @dom_import_simplexml($xml);
+                                                       if($dom===false) {
+                                                               $errors++;
+                                                               continue;
+                                                       }
+
+                                                       while($elem = $dom->getElementsByTagName("file")->item(0)) {
+                                                               $dom->removeChild($elem);
+                                                       }
+
+                                                       $xml = simplexml_import_dom($dom);
+                                                       array_to_xml($dis, $xml, "file");
+                                                       @$xml->asXML($metadata_dir . $f);
+                                                       $fixed++;
+                                               }
+                                       }
+                                       else {
+                                               $fcount_inv++;
+                                       }
+                               }
+                       }
+                       $result = array("time" => intval(current_time_millis() - $time), "fixed" => $fixed, "errors" => $errors);
+               }
+               catch(PEAR_Exception $e) {
+               }
+               echo "Content-type: text/html\n\n";
+               echo "<p>";
+               if(is_array($result)) {
+                       echo "Result of cleaning:<br/>\n";
+                       echo "$fcount files checked in " . $result['time'] . "ms of which $fcount_inv was invalid<br/>";
+                       echo "Fixed: " . $result['fixed'] . "<br/>";
+                       echo "Errors: " . $result['errors'] . "<br/>\n";
+
+               }
+               else if($result=="failed") {
+                       echo "It appears housecleaning failed, check your MPD settings";
+               }
+               else {
+                       echo "hmm.. somethings wrong, try again";
+               }
+               echo "</p><p><a href='config.php'>Back to configuration</a></p></body></html>\n";
+               echo "\n--ThisRandomString\n";
+       }
+       
+       
+       if(!isset($iamincluded)) {
+               if(isset($_GET['cover'])) get_cover();
+               else if(isset($_GET['review'])) get_review();
+               else if(isset($_GET['lyric'])) get_lyric();
+               else if(isset($_GET['pic'])) get_pic(); 
+               else if(isset($_GET['housecleaning'])) do_houseclean();
+               else if(isset($_GET['plrecommend'])) get_recommendations_from_playlist();
+               else {
+                       header("Content-Type: text/xml; charset=UTF-8");
+                       $xml = array_to_xml(array("result"=>"what do you want?"));
+                       echo $xml->asXML();
+                       exit();
+               }
+       }
+
+
+class xml2Array {
+  
+   var $arrOutput = array();
+   var $resParser;
+   var $strXmlData;
+  
+   /* parse to utf-8 */
+   function parse($strInputXML) {
+  
+          $this->resParser = xml_parser_create("UTF-8");
+
+           xml_set_object($this->resParser,$this);
+           xml_set_element_handler($this->resParser, "tagOpen", "tagClosed");
+          xml_parser_set_option($this->resParser, XML_OPTION_TARGET_ENCODING, "UTF-8");
+          
+           xml_set_character_data_handler($this->resParser, "tagData");
+      
+           $this->strXmlData = xml_parse($this->resParser,$strInputXML );
+           if(!$this->strXmlData) {
+               die(sprintf("XML error: %s at line %d",
+           xml_error_string(xml_get_error_code($this->resParser)),
+           xml_get_current_line_number($this->resParser)));
+           }
+                          
+           xml_parser_free($this->resParser);
+          
+           return $this->arrOutput;
+   }
+   function tagOpen($parser, $name, $attrs) {
+       $tag=array("name"=>$name,"attrs"=>$attrs);
+       array_push($this->arrOutput,$tag);
+   }
+  
+   function tagData($parser, $tagData) {
+          if(isset($this->arrOutput[count($this->arrOutput)-1]['tagData'])) 
+              $this->arrOutput[count($this->arrOutput)-1]['tagData'] .= $tagData;
+          else 
+              $this->arrOutput[count($this->arrOutput)-1]['tagData'] = $tagData;
+   }
+  
+   function tagClosed($parser, $name) {
+       $this->arrOutput[count($this->arrOutput)-2]['children'][] = $this->arrOutput[count($this->arrOutput)-1];
+       array_pop($this->arrOutput);
+   }
+}
+
+?>
diff --git a/player/metadata_cover.php b/player/metadata_cover.php
new file mode 100644 (file)
index 0000000..10e7b20
--- /dev/null
@@ -0,0 +1,129 @@
+<?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.
+*/
+
+       $amazon_cover_url = $amazon_base_url . "&Operation=ItemSearch&SearchIndex=Music&ResponseGroup=Images";
+       $cover_providers = array("directory_cover", "amazon_search_cover");
+
+       /* If you want to use this you'll need php-gd as well */
+       /* first %s will be replaced by Artist and second %s will be replaced by Album */
+       /* can be local path or url (http://someserver/path/%s/%s/cover.jpg will become
+          e.g. http://someserver/path/Escape The Fate/Dying Is Your Latest Fashion/cover.jpg */
+       //$directory_search_url = "/path/to/somehere/%s/%s.jpg";
+       $directory_search_url = false;
+
+       function amazon_search_cover($artist, $album) {
+               global $amazon_cover_url, $metadata_dir;
+
+               $xml = amazon_album_query($amazon_cover_url, $artist, $album);
+               if($xml) {
+                       if(isset($xml->Items[0])&&isset($xml->Items[0]->Item[0])) {
+                               $item = $xml->Items[0]->Item[0];
+                               $small = false;
+                               $small_name = false;
+                               $large = false;
+                               $large_name = false;
+                               
+                               if(isset($item->SmallImage[0])) {
+                                       $small = @file_get_contents($item->SmallImage[0]->URL[0]);
+                                       if($small) {
+                                               $small_name = escape_name($artist) . "-" . escape_name($album) . basename($item->SmallImage[0]->URL[0]);
+                                               if(!@file_put_contents($metadata_dir . $small_name, $small)) {
+                                                       $small = false;
+                                               }
+                                       }
+                               }
+                               if(isset($item->LargeImage[0])) {
+                                       $large = @file_get_contents($item->LargeImage[0]->URL[0]);
+                                       if($large) {
+                                               $large_name = escape_name($artist) . "-" . escape_name($album) . basename($item->LargeImage[0]->URL[0]);
+                                               if(!@file_put_contents($metadata_dir . $large_name, $large)) {
+                                                       $large = false;
+                                               }
+                                       }
+                               }
+                               else if(isset($item->MediumImage[0])) {
+                                       $large = @file_get_contents($item->MediumImage[0]->URL[0]);
+                                       if($large) {
+                                               $large_name = escape_name($artist) . "-" . escape_name($album) . basename($item->MediumImage[0]->URL[0]);
+                                               if(!@file_put_contents($metadata_dir . $large_name, $large)) {
+                                                       $large = false;
+                                               }
+                                       }
+                               }
+                               if($small && $large) {
+                                       $data = array();
+                                       $data['asin'] = $item->ASIN[0];
+                                       $data['thumbnail'] = $small_name;
+                                       $data['image'] =  $large_name;
+                                       return $data;
+                               }
+                       }
+               }
+               return false;
+       }
+
+       function directory_cover($artist, $album) {
+               global $directory_search_url, $metadata_dir;
+
+               if(!$directory_search_url) 
+                       return false;
+               
+               $artist = escape_name($artist);
+               $album = escape_name($album);
+
+               $name = sprintf($directory_search_url, $artist, $album);
+
+               return make_thumb_and_store_image($name, get_cover_base_name($artist, $album));
+       }
+
+       /* yay for me and function names */
+       function make_thumb_and_store_image($file, $store_bname) {
+               // only supports jpeg for now..
+               $max_size = 75; 
+               $image = @imagecreatefromjpeg($file);
+               if(!$image)
+                       return false;
+
+               $ix = imagesx($image);
+               $iy = imagesy($image);
+               $rx = $ry = $max_size;
+               
+               $max_i = $ix>$iy?$ix:$iy;
+               $res = array();
+               $res['image'] = basename($store_bname . ".jpg");
+               if(!@imagejpeg($image, $store_bname . ".jpg", 90)) {
+                       return false;
+               }
+               if($max_i>$max_size) {
+                       $ratio = (double)$max_i/(double)$max_size;
+                       $rx = (int)((double)$ix/$ratio);
+                       $ry = (int)((double)$iy/$ratio);
+                       $thumb = imagecreatetruecolor($rx,$ry);
+                       imagecopyresampled($thumb, $image, 0,0,0,0, $rx, $ry, $ix, $iy);
+                       $res['thumbnail'] = basename($store_bname . "_thumb.jpg");
+                       if(!@imagejpeg($thumb, $store_bname . "_thumb.jpg", 90)) {
+                               return false;
+                       }
+               }
+               else {
+                       $res['thumb'] = $res['image'];
+               }
+               return $res;
+       }
+?>
diff --git a/player/nowplaying.php b/player/nowplaying.php
new file mode 100644 (file)
index 0000000..401fad0
--- /dev/null
@@ -0,0 +1,107 @@
+<?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.
+*/
+
+       /* This is a slightly hidden feature
+        * Also goes to show how impractical it is to program against pitchfork core,
+        * but then again that never was the point :)
+        *
+        * usage: curl -s http://yourmpdhost/path/player/nowplaying.php
+        *
+        * in irssi you would do: 
+        * /alias np exec - -o curl -s http://..
+        *
+        * Never invoke it directly using php command, should always go through web-server
+        * (think of permissions!)
+        *
+        */
+
+       /* If you're using a password you have a little case with specifying the password thing
+        * The only solution as of yet is to not require a password to see now playing
+        *
+        * This does not grant access to anything else though 
+        *
+        * Just uncomment this line if that is something you want to do, I might add this option to the 
+        * configure screen some day though
+        *
+        */
+       $no_require_login = "true";
+
+
+
+       require_once("../inc/base.php");
+       $iamincluded = true;
+       require_once("../player/metadata.php"); 
+       header("Content-Type: text/plain");
+       
+       try {
+               $pl = get_playback();
+               $info = $pl->getCurrentSong();
+               $pl->disconnect();
+
+               $rlyric = "";
+
+               if(isset($info['Artist'])&&isset($info['Title'])) {
+                       $file = get_lyric_filename($info['Artist'], $info['Title']);
+                       $lyric = false; 
+                       if(file_exists($file)) {
+                               $lyric = _get_lyric_cache($file, $info['file']);
+                       }
+                       else {
+                               $lyric = @_get_lyric_lyricwiki($info['Artist'], $info['Title'], $info['file']);
+                       }
+
+                       if($lyric!==FALSE) {
+                               $lyric = simplexml_load_string($lyric);
+                               if($lyric&&$lyric->result->lyric) {
+                                       $lyric = (string) $lyric->result->lyric;
+                                       $lyric = explode("\n", $lyric);
+                                       $lines = count($lyric);
+                                       $tmp = "";
+                                       /* try until we hit something */
+                                       while(strlen($tmp)<=0&&$lines>0) {
+                                               $tmp = $lyric[rand(0, count($lyric)-1)];
+                                               if(strlen($tmp)) {
+                                                       $rlyric = substr(trim($tmp), 0, 60);
+                                                       if($rlyric) {
+                                                               $rlyric = " -~\"" . $rlyric . "\"";
+
+                                                       }
+                                               }
+                                               $lines--;
+                                       }
+                               }
+                       }
+                       echo "np: " . $info['Artist'] . " - " . $info['Title'] . $rlyric;
+               }
+               else {
+                       echo "np: ";
+                       if( isset($info['Artist']))
+                               echo $info['Artist'] . " - ";
+                       if(isset($info['Title'])) 
+                               echo $info['Title'] . " - ";
+                       if(isset($info['file']))
+                               echo $info['file'];
+                       else echo "not playing";
+               }
+       }
+       catch(PEARException $e) {
+               echo "error contacting mpd";
+       }
+       echo "\n";
+?>
diff --git a/player/openstrands.php b/player/openstrands.php
new file mode 100644 (file)
index 0000000..61e54ef
--- /dev/null
@@ -0,0 +1,54 @@
+<?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.
+*/
+
+       function strands_send_request($url) {
+               $STRANDS_URL = "https://www.mystrands.com/services";
+
+               $url = $STRANDS_URL . $url . "&subscriberId=5a05bc1dd3d608e96c8d336daf3544c5";
+               $xml = @file_get_contents($url);
+               $xml = @simplexml_load_string($xml);
+               return $xml;
+       }
+
+       /*
+        * artists must be an array
+        */
+       function strands_get_recommendations($artists) {
+               $url = "num=15&";
+               foreach($artists as $a) 
+                       $url .= "&name=" . urlencode($a);
+               $url = "/recommend/artists?" . $url;
+
+               $res = strands_send_request($url);
+
+               if(!$res)
+                       return false;
+
+               $ret = array();
+               if(!$res->SimpleArtist) return $ret;
+
+               foreach($res->SimpleArtist as $sa) {
+                       if($sa->ArtistName)
+                               $ret[] = (string)$sa->ArtistName;
+
+               }
+               return $ret;
+       }
+
+?>
diff --git a/player/preferences.js.php b/player/preferences.js.php
new file mode 100644 (file)
index 0000000..4550733
--- /dev/null
@@ -0,0 +1,69 @@
+<?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.
+*/
+
+       require_once("../inc/base.php");
+       header("Content-Type: text/javascript");
+       $delay = get_config("update_delay");
+       $disable_amazon = get_config("amazon_link_disable");
+       $metadata_disable = get_config("metadata_disable");
+
+       if(is_null($delay)||!is_numeric($delay)) {
+               $delay = 1;
+       }
+
+       echo "var update_delay = " . $delay . ";\n";
+
+       if(is_null($metadata_disable)) {
+               $metadata_disable = "false";
+       }
+       else $metadata_disable = "true";
+       echo "var metadata_disable = " . $metadata_disable . ";\n";
+
+       echo "var stop_button = ";
+       if(is_null(get_config("stop_button"))) {
+               echo "false";
+       }
+       else {
+               echo "true";
+       }
+       echo ";\n";
+
+
+       echo "var pl_entries = new Array('Pos', ";
+       
+       $selected = get_selected_plfields();
+       $length = count($pl_fields);
+       for($i=0; $i<$length; $i++) {
+               if($selected[$i])
+                       echo "'" . $pl_fields[$i] . "', ";
+       }
+
+       echo "'Time');\n";
+
+       echo "var SHOUT_URL=";
+       $shout = get_config("shout_url");
+       if(!is_null($shout))
+               echo "\"" . str_replace("\"", "\\\"", $shout) . "\"";
+       else echo "false";
+       echo ";\n";
+       
+       echo "var pagination_max = " . get_config("pagination", "0") . ";\n";
+
+       echo "\n";
+?>
diff --git a/std/base.css b/std/base.css
new file mode 100644 (file)
index 0000000..074ddb5
--- /dev/null
@@ -0,0 +1,520 @@
+body {
+       line-height: 19.2px;
+}
+
+a {
+       text-decoration: none;
+}
+
+tr, td, #disp_artist, #disp_album, #disp_info, p.disp, img.thumbnailart {
+       padding: 0px;
+       margin: 0px;
+}
+
+.main_container {
+       padding-left: 5px;
+       padding-bottom: 10px; 
+}
+
+#playlist {
+       line-height: 1em;
+       table-layout: fixed;
+       font-size: 0.99em;
+       border-width: 0px;
+       border-spacing: 0px;
+       cursor: default;
+}
+
+/* for konqueror */
+.pltemp {
+       color: #000001;
+}
+
+/* apply to td's in tr's following an element with id playlist */
+#playlist > tr > td { 
+       white-space: nowrap;
+       height: 1.3em;
+       border-width: 0px;
+       padding-left: 2px;
+}
+
+#content {
+       margin-left: 48px; 
+       margin-top: 106px;
+       position: relative;
+}
+
+/* this is complicated */
+ul.browser_field {
+       cursor: default;
+       font-size: 96%;
+       width: 30%; /* as small as possible */
+       line-height: 1.2em;
+       padding: 0px 0px 0px 3px;
+       margin: 2px 3px 0px 5px;
+       overflow: auto;
+       /*border: 1px solid #9db2b1;*/
+       float: left;
+}
+li {
+       /*padding: 0px 0px 0px 3px;*/
+       margin: 0px;
+}
+li[dirtype] {
+       background-position: top left;
+       background-repeat: no-repeat;
+       padding-left: 18px;
+}
+
+li[dirtype=parent] {
+       padding-left: 4px;
+}
+
+li[btype=search] {
+       padding-bottom: 7px;
+}
+
+li[btype=search]:last-child {
+       padding-bottom: 0px;
+}
+
+#posslider, #volslider {
+       width: 150px;
+}
+
+div.slider_main {
+       width: 100%;
+       height: 10px;
+       padding: 0px;
+       margin: 0px 5px 5px 5px;
+}
+div.slider_pointer {
+       position: relative;
+       left: 0px;
+       width: 5px;
+       height: 15px;
+       margin: -5px -5px 0px -5px;
+       padding: 0px 0px 5px 0px;
+}
+p.slider_txt {
+       margin: 0px 0px 0px 10px; 
+       cursor: default;
+}
+
+#disp_title {
+       font-size: larger;
+       padding: 0px;
+       margin: 15px 2px 0px 0px ;
+}
+
+div.overlay {
+       position: absolute;
+       padding: 10px;
+       display: none;
+}
+
+img.close {
+       position: absolute;
+       height: 15px;
+       width: 15px;
+       right: 5px;
+       top: 5px;
+       opacity: 0.85;
+}
+
+img.menu_button {
+       height: 30px;
+       width: 30px;
+       opacity: 0.80;
+       display: inline;
+}
+#debugbox {
+       position: fixed;
+       right: 0px;
+       bottom: 0px;
+       height: 200px;
+       width: 150px;
+       overflow: auto;
+       z-index: 100;
+       padding-left: 5px;
+       border: 1px solid #FFCC00;
+       background: #FFFF99;
+       color: black;
+}
+
+img.diritem {
+       height: 15px;
+       width: 15px;
+       position: relative;
+       right: 2px;
+}
+div.player_control {
+       position: fixed;
+       left: 3px;
+       right: 3px;
+       top: 0px;
+       padding: 0px 10px 0px 10px;
+       height: 101px;
+       z-index: 2;
+}
+div.pc_artist {
+       width: 400px;
+       left: 90px;
+       position: absolute;
+       padding-top: 5px;
+}
+div#albumart {
+       width: 80px;
+       height: 80px;
+       /*border: 1px solid red;*/
+       margin: 5px 10px 0px 0px;
+       float: left;
+}
+div.pc_ci {
+       position: absolute;
+       top: 25px;
+       left: 500px;
+}
+div.pc_sliders {
+       position: absolute;
+       left: 610px;
+       top: 2px;
+}
+div.selection_menu {
+       position: fixed;
+       top: 106px;
+       left: 3px;
+       width: 40px;
+       padding: 10px 0px 5px 5px;
+}
+#status_bar {
+       position: fixed;
+       left: 300px;
+       width: 300px;
+       top: 0px;
+       display:none;
+       height: 25px;
+       z-index: 10;
+}
+#status_bar_txt {
+       margin: 0px;
+       padding: 0px 2px 0px 5px;
+}
+img.status_working {
+       float: right;
+       margin: -15px 5px 0px 0px;
+       padding: 0px 2px 0px 5px;
+       display: none;
+}
+img.status_working[working] {
+       display: inline;
+}
+
+div.pc_other {
+       position: absolute;
+       right: 4px;
+       top: 0px;
+       font-size: smaller;
+       line-height: 13px;
+}
+
+hr.server_settings {
+       height: 0px;
+       margin-left: -5px; 
+       margin-right: 0px;
+}
+div.pc_settings {
+       position: absolute;
+       left: 615px;
+       top: 80px;
+       z-index: 5;
+       height: 20px;
+       font-size: 0.97em;
+}
+div.settings_header {
+       cursor: pointer;
+       margin: 0px 0px 0px 0px;
+       width: 152px;
+}
+div.settings_container {
+       display: none;
+       height: 200px;
+       top: 21px;
+       z-index: 5;
+       position: absolute;
+       width: 152px;
+}
+
+img.server_settings {
+       margin-bottom: -3px;
+}
+span.server_settings {
+       cursor: default;
+}
+.nomargin {
+       margin: 0px;
+}
+.nopadding {
+       padding: 0px;
+}
+p#settings_content {
+       margin: 0px;
+       padding-left: 5px;
+}
+
+input#quickadd {
+       margin: 0px 0px 0px -10px;
+       height: 13px;
+       width: 105px;
+}
+
+div#qa_suggestions {
+       margin: -3px 0px 0px -10px;
+       padding: 0px; 
+       min-width: 220px;
+       z-index: 100;
+       position: absolute;
+       display: none; /* yeah I want it, but I don't want *you* to see it! :p */
+       min-height: 15px;
+}
+p#qa_suggestions_txt {
+       padding: 0px;
+       margin: 0px;
+       font-size: 12px;
+       line-height: 1.3em;
+}
+
+span.qa_element {
+       display: block;
+}
+
+img.thumbnailart:hover {
+       cursor: pointer;
+}
+
+div.big_albumart {
+       position: fixed;
+       margin: auto;
+       left: 0px;
+       right: 0px;
+       bottom: 0px;
+       top: 0px;
+       width: 200px;
+       height: 50px;
+       padding: 10px;
+       z-index: 15;
+}
+img.big_albumart {
+       margin: auto;
+}
+div#sidebar_header {
+       top: 104px;
+       position: fixed;
+       display: none;
+       height: 20px;
+       width: 290px;
+       right: 3px;
+       z-index: 1;
+       padding: 0px 0px 0px 5px;
+}
+div#sidebar_display {
+       top: 127px;
+       display: none;
+       position: fixed;
+       right: 3px;
+       padding: 10px 5px 5px 10px;
+       max-width: 500px;
+       min-width: 280px;
+       overflow: auto;
+       bottom: 0px;
+       z-index: 1; 
+}
+
+#lyrics_refetch_button {
+       position: absolute;
+       right: 5px;
+       font-size: 0.95em;
+       margin: 0px; 
+}
+
+.fakelink {
+       cursor: pointer;
+}
+
+p.right_small {
+       text-align: right;
+       margin: 0px 0px 0px 0px;
+       padding: 0px 5px 0px 0px;
+}
+p.browse_type {
+       display: inline;
+       margin: 0px 2px 0px 2px;
+       padding: 0px 5px 0px 5px;
+       cursor: default;
+}
+
+ul#browser_act_buttons {
+       width: 95%;
+       clear: both;
+       padding: 0px;
+       margin: 0px 0px 0px 10px;
+}
+
+li.browser_button_add {
+       margin: 0px;
+       padding: 0px;
+       display: inline;
+       padding-left: 15px;
+       z-index: 20; 
+}
+
+/* only allow changing pos if not first elem */
+li.browser_button_add + li {
+       position: relative;
+}
+
+li.browser_button_add:hover {
+       cursor: default;
+}
+
+/* playlist hover, not the main playlist, playlists in the browser */
+div.playlist_hover {
+       display: none;
+       float: right;
+}
+
+li:hover div.playlist_hover {
+       color: red;
+       display: inline;
+}
+
+div.popup {
+       position: absolute;
+       margin-top: -10px;
+       z-index: 10;
+       padding: 2px 5px 5px 5px;
+       display: none;
+       cursor: default;
+}
+
+.fullscreen {
+       position: fixed;
+       left: 0px;
+       top: 0px; 
+       z-index: 20;
+       background-color: red;
+}
+
+ul.playlist_popup {
+       margin: 0px;
+       padding: 2px;
+}
+li.playlist_popup {
+       display: inline;
+       margin-right: 2px;
+       cursor: pointer;
+}
+span.playlist_popup {
+       cursor: pointer;
+       margin: 2px;
+}
+
+div.moving_box {
+       position: relative;
+       overflow: hidden;
+       font-size: 0.96em;
+       cursor: move;
+}
+
+div#streaming_display {
+       position: absolute;
+       top: 2px;
+       margin: 0px;
+       padding: 0px;
+       right: 120px;
+       width: 80px;
+       line-height: 13px;
+}
+
+div#streaming_display li {
+       list-style-type: none;
+       padding-left: 4px;
+       padding-right: 4px;
+}
+
+div#pagination, div#pagination_options {
+       position: fixed;
+       bottom: 0px; 
+       left: 50px; 
+       height: 25px;
+       padding: 0px;
+       cursor: default;
+       max-width: 680px;
+       display: none;
+}
+div#pagination_options {
+       left: 3px; 
+       width: 47px;
+       display: block;
+}
+ul#pagination_list {
+       padding: 0px; 
+       margin: 4px 0px 2px 2px; 
+       overflow: hidden; 
+}
+ul#pagination_list > li { /* li following pagination ul */
+       display: inline;
+       width: 10px; 
+       margin: 2px;
+       cursor: pointer;
+}
+
+/* just a spacer at the bottom to avoid last row 
+ * disappearing under the pagination list */
+div#pagination_spacer {
+       height: 25px;
+}
+
+img.pagination_options {
+       margin: 4px 0px 0px 2px;
+       padding: 0px;
+       cursor: pointer;
+       opacity: 0.80;
+}
+
+span.plse {
+       display: block;
+       line-height: 110%;
+       cursor: pointer;
+       padding-left: 20px;
+       background-position: top left;
+       background-repeat: no-repeat;
+}
+
+li.menuitem {
+       list-style-type: none;
+       padding-left: 4px;
+       padding-right: 4px;
+       border-top: none;
+}
+
+
+ul.recommended {
+       padding-left: 10px;
+}
+
+ul.recommended > li {
+       cursor: pointer;
+}
+
+ul.recommended > li > ul {
+       padding-left: 15px;
+       display: none;
+}
+ul.recommended > li[open] > ul {
+       display: block;
+}
+
+img.menu_button:hover, img.close:hover, img.pagination_options:hover {
+       opacity: 1.0;
+}
diff --git a/std/browser.js b/std/browser.js
new file mode 100644 (file)
index 0000000..4c3f039
--- /dev/null
@@ -0,0 +1,857 @@
+/* 
+    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.
+*/
+
+/* this is so broken that I can hardly describe it */
+
+var BROWSER_ID = "browser_"; 
+var BROWSE_FILE = 0;
+var BROWSE_ARTIST = 1;
+var BROWSE_ALBUM = 2;
+var BROWSE_SEARCH = 3;
+
+var blist;
+
+var BROWSE_SEARCH_OPTS;
+
+function BrowserList(browser) {
+       this.open_name = "";
+       this.open = null;
+       this.first = null;
+       this.type = BROWSE_FILE;
+       this.type_name = new Array();
+       this.type_name[BROWSE_FILE] = LANG.FILESYSTEM;
+       this.type_name[BROWSE_ARTIST] = LANG.ARTIST;
+       this.type_name[BROWSE_ALBUM] = LANG.ALBUM;
+       this.browser = browser;
+       this.search = new KeySearch(null); // this is for key-typing in the fields, not the search field
+       this.search_select = null; // jeje.. search field et al. 
+       this.search_field = null;
+       this.search_opt = null;
+       this.act_buttons = null;
+       this.open_popup = null;
+       this.eventListener = null;
+}
+
+function BrowserWindow(value) {
+       this.value = value;
+       this.next = null;
+}
+
+BrowserList.prototype.get_window_by_value = function (elem) {
+       var w = this.first;
+       while(w!=null) {
+               if(w.value==elem) 
+                       return w;
+               w = w.next;
+       }
+       return null;
+}
+BrowserList.prototype.get_last_window = function() {
+       var w = this.first;
+       while(w.next!=null)
+               w = w.next;
+       return w;
+}
+
+BrowserList.prototype.get_previous_window = function(node) {
+       var w = this.first;
+       while(w!=null&&w.next!=node)
+               w = w.next;
+       return w;
+}
+BrowserList.prototype.get_next_window = function(node) {
+       var w = this.first;
+       while(w) {
+               if(w==node) 
+                       return w.next;
+               w = w.next;
+       }
+       return null;
+}
+
+function dirlist_init() {
+       BROWSE_SEARCH_OPTS = new Array(LANG.ANYTAG, LANG.ARTIST, LANG.TITLE, 
+                               LANG.ALBUM, LANG.GENRE, LANG.FILENAME, LANG.COMPOSER,
+                               LANG.PERFORMER, LANG.DATE, LANG.LYRICS);
+
+       remove_children(pl_overlay_write);
+       send_command("dirlist", dirlist_init_handler);
+}
+
+function dirlist_init_handler(response) {
+       if(response=="failure") {
+               show_status_bar(LANG.E_FAILED_LOAD_DIR);
+       }
+       else {
+         var ul;
+         var ul_tmp = null;
+         blist = new BrowserList(pl_overlay_write);
+         var stuff = create_node("div");
+         stuff.className = "browse_type_container";
+
+         for(var i=0; i<blist.type_name.length; i++) {
+               var n = create_node("p");
+               n.className = "browse_type";
+               n.appendChild(create_txt(blist.type_name[i]));
+               stuff.appendChild(n);
+               if(i==BROWSE_FILE)
+                       add_listener(n, "click", browse_file_init);
+               else if(i==BROWSE_ARTIST)
+                       add_listener(n, "click", browse_artist_init);
+               else if(i==BROWSE_ALBUM) 
+                       add_listener(n, "click", browse_album_init);
+               add_listener(n, "mousedown", stop_event);
+               
+         }
+
+         var search = blist.search_select = create_search_choices(BROWSE_SEARCH_OPTS, browse_search_change);
+         stuff.appendChild(search);  
+         var opt = blist.search_opt = create_node("option", "bs_search");
+         opt.value = "-1";
+         opt.appendChild(create_txt("Search:"));
+         insert_first(opt, search);
+         search.selectedIndex = 0;
+         search.id ="browse_search_select";
+
+         blist.search_field = search = create_node("input", "browse_search_field");
+         search.className = "browse_type";
+         search.size = "20";
+         /* make sure it doesn't get captured by someone else */
+         add_listener(search, "keydown", stop_propagation);
+         add_listener(search, "keyup", browser_search_field_keyhandler);
+         search.disabled = "disabled";
+         stuff.appendChild(search);
+
+         var update = create_node("p");
+         update.className = "browse_type";
+         update.appendChild(create_txt(LANG.UPDATE_DB));
+         add_listener(update, "click", send_update_db_cmd);
+         stuff.appendChild(update);
+
+         blist.browser.appendChild(stuff);
+
+         var buttons = blist.act_buttons = create_node("ul", "browser_act_buttons");
+         var btn;
+
+         var bl = new Array();
+         for(var i=0; i<BROWSER_NUM; i++) {
+               ul = create_node("ul", BROWSER_ID + i);
+               bl[i] = ul;
+               ul.className = "browser_field";
+               blist.browser.appendChild(ul);
+               setup_dirlist_listeners(ul);
+
+               btn = create_node("li", "browser_btn_add_" + i);
+               btn.className = "browser_button_add";
+               if(i>0)
+                       btn.style.left = 29*i + "%";
+               btn.setAttribute("field", i);
+               btn.appendChild(create_txt(LANG.ADD));
+               add_listener(btn, "click", browser_multi_add); 
+               buttons.appendChild(btn);
+         }
+         add_listener(buttons, "mousedown", stop_event);
+         blist.browser.appendChild(buttons);
+         /* link it up */
+         var last = null;
+         for(var i=0; i<BROWSER_NUM; i++) {
+               var bw = new BrowserWindow(bl[i]);
+               if(i==0) 
+                       blist.first = last = bw;
+               else 
+                       last = last.next = bw;
+         }
+
+         browser_fill_entry(blist.first.value, response, 0, "");
+       }
+}
+
+/* opts: array with options
+   onchange_cb: what to call when something changes
+   return: search box 
+*/
+function create_search_choices(opts, onchange_cb) {
+         var search = create_node("select");
+         search.className = "browse_type";
+         add_listener(search, "change", onchange_cb);
+         var opt = null;
+         for(var i=0; i<opts.length; i++) {
+               opt = create_node("option");
+               opt.value = i;
+               opt.appendChild(create_txt(opts[i]));
+               search.appendChild(opt);
+         }
+         return search;
+}
+
+function browse_file_init() {
+       browse_style_changed(BROWSE_FILE);
+}
+function browse_album_init() {
+       browse_style_changed(BROWSE_ALBUM);
+}
+function browse_artist_init() {
+       browse_style_changed(BROWSE_ARTIST);
+}
+function browse_search_change(e) {
+       /* this is when opening the search browser */
+       if(blist.type!=BROWSE_SEARCH) {
+               var w = blist.first; 
+               while(w) {
+                       browser_clear_window(w.value);
+                       w = w.next;
+               }
+               browse_style_changed(BROWSE_SEARCH);
+               browser_search_field_enable();
+       }
+       else {
+       }
+}
+
+function browse_style_changed(type) {
+       if(type==blist.type)
+               return;
+
+       /* this is when switching back to normal view from search */
+       if(blist.type==BROWSE_SEARCH) {
+               var w = blist.first;
+               while(w) {
+                       w.value.style.display = "";
+                       w.value.style.width = "";
+                       w = w.next;
+               }
+               browser_search_field_clear();
+               insert_first(blist.search_opt, blist.search_select);
+               blist.search_opt.selected = "selected";
+               blist.search_select.blur(); // just in case it is selected..
+               browser_act_buttons_display(true);
+       }
+       else if(type==BROWSE_SEARCH) {
+               // only show first
+               var w = blist.first.next;
+               while(w) {
+                       w.value.style.display = "none";
+                       w = w.next;
+               }
+       
+               blist.first.value.style.width = "95%";
+               browser_act_buttons_display(false);
+               remove_node(blist.search_opt);
+       }
+       blist.type = type;
+       if(type!=BROWSE_SEARCH)
+               browser_open_single_item(blist.first.value);
+}
+function setup_dirlist_listeners(ul) {
+       add_listener(ul, "click", browser_click);
+       /* no text selection support: */
+       add_listener(ul, "mousedown", prevent_dirlist_default);
+}
+
+function prevent_dirlist_default(e) {
+       if(e.target.hasAttribute("diritem")||e.target.parentNode.hasAttribute("diritem"))
+               stop_event(e);
+}
+
+function browser_fill_entry(entry, resp, strip, parent_dir) {
+         if(!strip) 
+               strip = 0;
+         var p = null;
+         var type = null;
+
+         /* adding parent */
+         if(strip>0&&resp) { 
+               if(blist.type==BROWSE_FILE) {
+                       var tmp = add_li(entry, "< ..", entry.id + "_parent");
+                       tmp.setAttribute("dirtype", "parent");
+                       var name;
+
+                       out: 
+                       for(var t in resp) {
+                               for(var i in resp[t]) {
+                                       name = resp[t][i];
+                                       break out; 
+                               }
+                       }
+
+                       if(name.substring) {
+                               name = name.substring(0,strip-1);
+                               tmp.setAttribute("diritem", name);
+                               entry.setAttribute("dirlist", name);
+                       }
+                       entry.setAttribute("parent_dir", parent_dir);
+               }
+               else {
+                       entry.setAttribute("dirlist", blist.open_name);
+                       entry.setAttribute("parent_dir", parent_dir);
+               }
+         }
+         else {
+               entry.setAttribute("dirlist", "");
+         }
+         var strip_name = null;
+         var rtype = null;
+         for(var type in resp) {
+               for(var idx in resp[type]) {
+                       name = resp[type][idx];
+                       rtype = type;
+                       if(type=="filelist") {
+                               if(typeof(name['Title'])!='undefined'&&name["Title"].length) {
+                                       strip_name = name["Title"];
+                                       name = name["file"];
+                               }
+                               else {
+                                       name = name["file"];
+                                       strip_name = name.substring(name.lastIndexOf(DIR_SEPARATOR)+1);
+                               }
+                               rtype = "file";
+                       }
+                       else if(!name.substring) {
+                               continue;
+                       }
+                       else strip_name = name;
+
+                       if(type=="directory") {
+                               strip_name = name.substring(strip);
+                       }
+                       else if(type=="file"&&name.lastIndexOf(DIR_SEPARATOR)>=0) {
+                               strip_name = name.substring(name.lastIndexOf(DIR_SEPARATOR)+1);
+                       }
+
+                       var l = create_node("li", null, strip_name);
+                       l.setAttribute("diritem", name);
+                       l.setAttribute("dirtype", rtype);
+                       entry.appendChild(l);
+                       if(type=="playlist") 
+                               add_playlist_hover(l);
+               }
+        }
+}
+
+function add_playlist_hover(l) {
+       var h = create_node("div");
+       h.className = "playlist_hover";
+       var img = create_node("img");
+       img.src = IMAGE.BROWSER_PLAYLIST_REMOVE; 
+       img.className = "fakelink";
+       h.appendChild(img);
+       add_listener(img, "click", show_remove_playlist);
+       insert_first(h,l);
+}
+
+function show_remove_playlist(e) {
+       var t = e.target.parentNode.parentNode;
+       var name = t.getAttribute("diritem");
+       var content = document.createDocumentFragment();
+       content.appendChild(create_txt(LANG.CONFIRM_REMOVE + " '" + name + "'?"));
+       var yes = create_node("span");
+       yes.className = "fakelink";
+       yes.appendChild(create_txt(" Yes,"));
+       content.appendChild(yes);
+
+       var no = create_node("span");
+       no.className = "fakelink";
+       no.appendChild(create_txt(" No"));
+       content.appendChild(no);
+
+       if(blist.open_popup)
+               destroy_open_popup();
+       var pop = new Popup(document.body, content);
+       blist.open_popup = pop;
+       blist.eventListener = add_listener(document.body, "click", destroy_open_popup);
+       add_listener(yes, "click", function() { remove_playlist(t) ; destroy_open_popup(); });
+       add_listener(no, "click", destroy_open_popup);
+       pop.popup.style.padding = "5px 5px 5px 5px";
+       pop.popup.style.position = "absolute"; 
+       pop.popup.style.left = "80px";
+       pop.popup.style.top = "500px";
+       pop.popup.style.margin = "0 auto"; 
+       pop.show();
+}
+
+function destroy_open_popup() {
+       var blist = window.blist; // jic
+       if(blist.open_popup) {
+               blist.open_popup.destroy();
+               blist.open_popup = null;
+       }
+       if(blist.eventListener) {
+               blist.eventListener.unregister();
+               blist.eventListener = null;
+       }
+}
+
+function remove_playlist(elem) {
+       var item = elem.getAttribute("diritem");
+       send_command("playlist_rm=" + encodeURIComponent(item), remove_playlist_cb);
+       remove_node(elem);
+}
+
+function remove_playlist_cb(e) {
+       if(e!="ok") {
+               show_status_bar(LANG.E_FAILED_ADD);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+}
+
+function browser_click(e) {
+       stop_event(e);
+       var elem = e.target;
+
+       if(!elem||!elem.hasAttribute) return;
+
+       /* test if we got one of the direct children as targets */
+       if(elem.parentNode&&elem.parentNode.hasAttribute("diritem"))
+               elem = elem.parentNode;
+       
+       if(elem.hasAttribute&&elem.hasAttribute("diritem")) {
+               e.stopPropagation();
+               var container = elem.parentNode;
+               blist.search.focus = container;
+               if(e.ctrlKey||e.metaKey) {
+                       if(is_node_selected(elem))
+                               unselect_node(elem);
+                       else 
+                               select_node(elem);
+                       // safari workaround
+                       container.className = container.className;
+               }
+               else if (e.shiftKey) {
+                       var sel_node = find_first_selected_node(container, elem);
+                       unselect_all_nodes(container);
+                       if(sel_node==null) {
+                               select_node(elem);
+                       }
+                       else {
+                               if(elem.hasAttribute("needle")) 
+                                       select_range(elem, sel_node);
+                               else 
+                                       select_range(sel_node, elem);
+                       }
+                       if(elem.hasAttribute("needle")) 
+                               elem.removeAttribute("needle");
+                       // safari workaround
+                       container.className = container.className;
+               }
+               else {
+                       browser_open_single_item(container, elem, e.detail==2);
+               }
+       }
+}
+
+function browser_open_single_item(container, elem, doubleclick) {
+       unselect_all_nodes(container);
+       var type;
+       if(elem) {
+               type = elem.getAttribute("dirtype");
+               select_node(elem);
+               // safari workaround
+               container.className = container.className;
+       }
+       else {
+               type = "other";
+       }
+       var b_id = blist.get_window_by_value(container);
+
+       if(!doubleclick) {
+          if(type=="directory"||type=="artist"||type=="album") {
+               var diritem = elem.getAttribute("diritem")
+               
+               blist.open = b_id.next;
+               blist.open_name = diritem;
+
+               /* test if already open */
+               if(blist.open!=null) {
+                       // this item is already opened
+                       if(blist.open.value.getAttribute("dirlist")==diritem)
+                               return;
+               }
+
+               /* remove all entries in following rows */
+               var tmp = b_id.next;
+               while(tmp!=null) {
+                       if(tmp.value.hasAttribute("dirlist"))
+                               tmp.value.removeAttribute("dirlist");
+                       if(tmp.value.hasAttribute("parent_dir"))
+                               tmp.value.removeAttribute("parent_dir");
+                       remove_children(tmp.value);
+                       tmp = tmp.next;
+               }
+               if(blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM){
+                       var artist, album;
+                       if(blist.open == blist.get_last_window()) {
+                               if(blist.type==BROWSE_ARTIST) {
+                                       artist = b_id.value.getAttribute("dirlist");
+                                       album = diritem;
+                               }
+                               else {
+                                       album = b_id.value.getAttribute("dirlist");
+                                       artist = diritem;
+                               }
+                               send_command("searchfile&artist=" + encodeURIComponent(artist) + "&album=" + encodeURIComponent(album), browser_click_cb);
+                               return;
+                       }
+                       else if(blist.open==null) {
+                               // don't open
+                               return;
+                       }
+                       // else do as usual...
+               }
+               send_command("dirlist=" + encodeURIComponent(blist.open_name) + "&type=" + blist.type, browser_click_cb);
+          }
+          else if(type=="parent"||type=="other") {
+               blist.open = blist.first;
+               if(type=="parent")
+                       blist.open_name = blist.first.value.getAttribute("parent_dir");
+               else blist.open_name = "";
+
+               var w = blist.first;
+               if(type=="parent") // || switch type...
+                       while(w!=null&&w!=b_id) 
+                               w = w.next;
+               while(w!=null){
+                       browser_clear_window(w.value);
+                       w = w.next;
+               }
+
+               if(blist.first.value.getAttribute("dirlist")!=""||type=="other") { // already at top
+                       send_command("dirlist=" + encodeURIComponent(blist.open_name) + "&type=" + blist.type, browser_click_cb);
+               }
+          }
+       }
+       else if(doubleclick&&type!="parent") {
+               var diritem = elem.getAttribute("diritem")
+               blink_node(elem, DEFAULT_BLINK_COLOR);
+               if(elem.getAttribute("dirtype")=="playlist") {
+                       send_command("playlist_load=" + encodeURIComponent(diritem), null, LANG.WAIT_ADDING);
+               }
+               else if((blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM)&&elem.getAttribute("dirtype")!="file") {
+                       var artist, album;
+                       if(b_id == blist.first.next) {
+                               if(blist.type==BROWSE_ARTIST) {
+                                       artist = b_id.value.getAttribute("dirlist");
+                                       album = diritem;
+                               }
+                               else {
+                                       album = b_id.value.getAttribute("dirlist");
+                                       artist = diritem;
+                               }
+                               send_command("searchadd&artist=" + encodeURIComponent(artist) + "&album=" +
+                                       encodeURIComponent(album), browser_add_cb, LANG.WAIT_ADDING);
+                       }
+                       else if(b_id==blist.first) {
+                               var cmd = "searchadd&";
+                               if(blist.type==BROWSE_ARTIST) {
+                                       cmd+="artist=";
+                               }
+                               else {
+                                       cmd+="album=";
+                               }
+                               cmd += encodeURIComponent(diritem);
+                               send_command(cmd, browser_add_cb, LANG.WAIT_ADDING);
+                       }
+               }
+               else {
+                       // else do as usual...
+                       send_command("add=" + encodeURIComponent(diritem), browser_add_cb, LANG.WAIT_ADDING);
+               }
+       }
+}
+
+function browser_click_cb(response) {
+       var strip = 0;
+       var parent_dir = null;
+       if(blist.open==null) {
+               // got to move it
+               var last = blist.get_last_window();
+               var first = blist.first; 
+               var prev = blist.get_previous_window(last);
+               parent_dir = prev.value.getAttribute("dirlist");
+               var tmp = first.value;
+
+               while(tmp.hasChildNodes()) {
+                       remove_node(tmp.lastChild);
+               }
+               remove_node(tmp);
+               insert_after(tmp, last.value);
+               last.next = first;
+               blist.first = first.next;
+               first.next = null;
+               blist.open = blist.get_last_window();
+       }
+       else if (blist.open==blist.first) {
+               parent_dir = blist.first.value.getAttribute("parent_dir");
+               if(parent_dir)
+                       parent_dir = parent_dir.substring(0,parent_dir.lastIndexOf(DIR_SEPARATOR));
+
+               var first = blist.first;
+               var last = blist.get_last_window();
+               var prev = blist.get_previous_window(last);
+               var tmp = last.value;
+               while(tmp.hasChildNodes())
+                       remove_node(tmp.lastChild);
+               remove_node(tmp);
+               insert_before(tmp, first.value)
+               blist.first = last;
+               last.next = first;
+               prev.next = null;
+               blist.open = blist.first;
+       }
+       var b = blist.open;
+       var prev = blist.get_previous_window(b);
+       if(parent_dir==null&&prev!=null) {
+               parent_dir = prev.value.getAttribute("dirlist");
+       }
+       else if(parent_dir==null) {
+               parent_dir = "";
+       }
+
+       if(blist.open_name.length>0)
+               strip = blist.open_name.length + 1;
+
+       browser_fill_entry(b.value, response, strip, parent_dir);
+       blist.open_name = "";
+       blist.open = null;
+}
+
+function browser_add_cb(response) {
+       if(response!="ok") {
+               show_status_bar(LANG.E_FAILED_ADD);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+}
+
+function pl_overlay_open_cb(height) {
+       var elem = null;
+       var i=0; 
+       var w = blist.first;
+       while(w != null ) {
+               /* this makes room for the top (filesys, artist, search et al.) and bottom (add etc) */
+               w.value.style.height = (height - 40)+ "px";
+               w = w.next;
+       }
+       blist.search.listener = keyboard_register_listener(browser_key_search, KEYBOARD_MATCH_ALL, KEYBOARD_NO_KEY, true);
+       // TODO, maybe set broswer_focus
+}
+
+function pl_overlay_close_cb() {
+       keyboard_remove_listener(blist.search.listener);
+}
+
+function browser_key_search(e) {
+       if(!blist.search.focus) {
+               return;
+       }
+       var clear_time = 1000;
+
+       var c = blist.search.focus.childNodes;
+
+       if(e.keyCode==38||e.keyCode==40||e.keyCode==37||e.keyCode==39) {
+               var it = null;
+               // find first selected node
+               for(var i=0; i < c.length; i++) { 
+                       if(is_node_selected(c[i])) {
+                               it = c[i];
+                               break;
+                       }
+               }
+               if(it) {
+                       var win = null;
+                       if(e.keyCode==40&&it.nextSibling) {
+                               it = it.nextSibling;
+                       }
+                       else if(e.keyCode==38&&it.previousSibling) {
+                               it = it.previousSibling;
+                       }
+                       else if(e.keyCode==37) { // left
+                               // todo
+                               //win = blist.get_previous_window(blist.get_window_by_value(blist.search.focus));
+                               it = null;
+                       }
+                       else if(e.keyCode==39) { // right
+                               // todo
+                               //win = blist.get_next_window(blist.get_window_by_value(blist.search.focus));
+                               it = null;
+                       }
+                       else it = null;
+                       if(it) {
+                               unselect_all_nodes(blist.search.focus);
+                               select_node(it);
+                               scrollIntoView(it, false);
+                               blist.search.item = it;
+                       }
+                       else if (win) {
+                               win = win.value;
+                               blist.search.focus = win;
+                               unselect_all_nodes(win);
+                               select_node(win.firstChild)
+                       }
+                       blist.search.term = "";
+               }
+       }
+       else {
+               var s = blist.search.term + String.fromCharCode(e.keyCode);
+               blist.search.term = s = s.toLowerCase();
+
+               for(var i=0; i<c.length; i++) {
+                       var c_name = get_node_content(c[i]);
+                       if(!c_name||!c_name.toLowerCase)
+                               continue;
+                       c_name = c_name.toLowerCase();
+                       if(c_name.indexOf(s)==0) {
+                               unselect_all_nodes(blist.search.focus);
+                               select_node(c[i]);
+                               scrollIntoView(c[i], false);
+                               blist.search.item = c[i];
+                               break;
+                       }
+               }
+       }
+       clearTimeout(blist.search.timer);
+       blist.search.timer = setTimeout(browser_clear_search_term, clear_time);
+}
+function browser_clear_window(win) {
+       remove_children(win);
+       if(win.hasAttribute("dirlist"))
+               win.removeAttribute("dirlist");
+       if(win.hasAttribute("parent_dir"))
+               win.removeAttribute("parent_dir");
+}
+
+/* clear key-search  */
+function browser_clear_search_term() {
+       blist.search.term = "";
+       if(blist.search.focus&&blist.search.item) {
+               browser_open_single_item(blist.search.focus, blist.search.item); 
+               blist.search.item = null;
+       }
+}
+
+function KeySearch(listener) {
+       this.listener = listener;
+       this.focus = null;
+       this.term = "";
+       this.timer = null;
+}
+
+/* clear search field */
+function browser_search_field_clear() {
+       blist.search_field.value = "";
+       blist.search_field.disabled = "disabled";
+       blist.search_field.blur();
+}
+
+function browser_search_field_enable() {
+       blist.search_field.disabled = "";
+       blist.search_field.focus();
+}
+
+function browser_search_field_keyhandler(e) {
+       if(e.keyCode&&e.keyCode==RETURN_KEY_CODE) {
+               if(blist.search_field.value.trim().length>0) {
+                       send_command("metasearch=" + blist.search_select.value + "&s=" + encodeURIComponent(blist.search_field.value), 
+                                       browser_search_field_keyhandler_cb, LANG.WAIT_SEARCHING);
+               }
+               else {
+                       remove_children(blist.first.value);
+               }
+       }
+}
+
+function browser_search_field_keyhandler_cb(resp) {
+       var dst = blist.first.value;
+       remove_children(dst);
+       if(typeof(resp)!='undefined'&&resp!="failed") {
+               for(var i=0; i<resp.length; i++) {
+                       var file = resp[i]["file"];
+                       var artist = resp[i]["Artist"];
+                       var title = resp[i]["Title"];
+                       var name = "";
+                       if(title==null||!title.length) {
+                               name = file.substring(file.lastIndexOf(DIR_SEPARATOR)+1);
+                       }
+                       else {
+                               name = artist + " - " + title;
+                       }
+
+                       var l = add_li(dst, name, dst.id + "_" + i);
+                       l.setAttribute("diritem", file);
+                       l.setAttribute("dirtype", "file");
+                       /* used to match css selector */
+                       l.setAttribute("btype", "search");
+                       l.appendChild(create_node("br"));
+                       var fspan = create_node("span", null, file);
+                       l.appendChild(fspan);
+               }
+       }
+}
+
+function browser_act_buttons_display(visible) {
+       var btn = blist.act_buttons.childNodes;
+       for(var i=1; i<btn.length; i++) {
+               btn[i].style.display = (visible?"":"none");
+       }
+}
+
+
+function browser_multi_add(e) {
+       var target = e.target;
+       if(!target||!target.hasAttribute("field")) 
+               return;
+       var field = parseInt(target.getAttribute("field"));
+       var container = blist.first;
+       /* find what field we belong to */
+       for(var i=0; i<field&&container; i++) {
+               container = container.next;
+       }
+       if(container) {
+               var add = get_attribute_from_selected_elems(container.value, "diritem");
+               var type = get_attribute_from_selected_elems(container.value, "dirtype");
+               if(add.length<=0||add.length!=type.length) { // add.length must always equal type.length
+                       //debug("a: " + add.length + ", t: " + type.length);
+                       return;
+               }
+               var cmd="";
+               if(blist.type==BROWSE_ARTIST||blist.type==BROWSE_ALBUM) {
+                       if(container==blist.first.next) {
+                               if(blist.type==BROWSE_ARTIST)
+                                       cmd+="baseartist:";
+                               else cmd+="basealbum:";
+                               var dirlist = container.value.getAttribute("dirlist");
+                               cmd+=dirlist+"\n";
+                       }
+               }
+               for(var i=0; i<add.length; i++) {
+                       if(type[i]=="parent")
+                               continue;
+                       cmd+= type[i];
+                       cmd+= ":";
+                       cmd+= add[i];
+                       cmd+= "\n";
+               }
+               blink_node(target, DEFAULT_BLINK_COLOR);
+               send_command("ma", browser_multi_add_cb, LANG.WAIT_ADDING, false, cmd);
+       }
+}
+
+function browser_multi_add_cb(res) {
+       if(res!="ok") {
+               show_status_bar(LANG.E_FAILED_ADD);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+}
diff --git a/std/collection.js b/std/collection.js
new file mode 100644 (file)
index 0000000..1cbf5a1
--- /dev/null
@@ -0,0 +1,201 @@
+/* 
+    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.
+*/
+
+/* HashMap implementation, guess it's more of a key-lookup-linked-list though */
+
+function HashNode(key, val) {
+       this.key = key;
+       this.value = val;
+       this.next = null;
+}
+function HashMap() {
+       this.header = null;
+}
+
+/* 
+ * Usage: var it = hashmap.iterator();
+ * var tmp;
+ * while(tmp = it.next()); 
+ */
+function HashMapIterator(first) {
+       this.c = first;
+}
+
+HashMapIterator.prototype.next = function() {
+       var ret = this.c;
+       if(this.c != null) {
+               ret = ret.value;
+               this.c = this.c.next;
+       }
+       return ret;
+}
+
+/* get an entry from the list */
+HashMap.prototype.get = function(key) {
+       var n = this.header;
+       while(n!=null) {
+               if(n.key==key) 
+                       return n.value;
+               n = n.next;
+       }
+       return null;
+}
+
+/* Removes an entry from the list and returns it */
+HashMap.prototype.remove = function(key) {
+       if(this.header==null) 
+               debug("wops, header null");
+       if(key==this.header.key) {
+               var tmp = this.header;
+               this.header = this.header.next;
+               return tmp.value;
+       }
+       else {
+               var n = this.header;
+               while(n.next != null) {
+                       if(n.next.key == key) {
+                               var ret = n.next;
+                               n.next = n.next.next;
+                               return ret.value;
+                       }
+                       n = n.next;
+               }
+       }
+       return null;
+}
+
+/* put a new entry in the list */
+HashMap.prototype.put = function(key, value) {
+       var node = new HashNode(key,value);
+       var next = this.header;
+       if(next==null) {
+               this.header = node;
+       }
+       else {
+               while(next.next!=null)
+                       next = next.next;
+               next.next = node;
+       }
+}
+
+HashMap.prototype.insert = function(key, value) {
+       var node = new HashNode(key,value);
+       node.next = this.header;
+       this.header = node;
+}
+
+HashMap.prototype.iterator = function() {
+       return new HashMapIterator(this.header);
+}
+
+
+/* A proper hashtable implementation 
+*/
+function Hashtable() {
+       this.data = new Object();
+       this._size = 0;
+}
+
+/* clears the hashtable */
+Hashtable.prototype.clear = function() {
+       for(var key in this.data)
+               delete this.data[key];
+       this._size = 0;
+}
+
+/* tests whether the specified value is in this hashtable 
+  returns true if it does, falseotherwise */
+Hashtable.prototype.contains = function(obj) {
+       if(obj==null) return false;
+
+       for(var opt in this.data) {
+               if(obj == this.data[opt])
+                       return true;
+       }
+       return false;
+}
+
+/* Tests whether the specified key is in this hashtable  
+   returns true if it does, false otherwise*/
+Hashtable.prototype.containsKey = function(key) {
+       return typeof this.data[key] != "undefined" && this.data[key] != null;
+}
+
+Hashtable.prototype.put = function (key, value) {
+       this.data[key] = value;
+       this._size++;
+}
+
+/** puts all the in to this hashtable */
+Hashtable.prototype.putAll = function(map) {
+       for(var key in map) {
+               this.data[key] = map[key];
+               this._size++;
+       }
+}
+
+Hashtable.prototype.get = function (key) {
+       return this.data[key];
+}
+
+Hashtable.prototype.isEmpty = function() {
+       return this._size == 0;
+}
+/**
+ remove the object and key from this hashtable,
+ returns the object if it's there, null otherwise
+ */
+Hashtable.prototype.remove = function(key) {
+       if(!this.containsKey(key))
+               return null;
+       var ret = this.data[key];
+       delete this.data[key];
+       this._size--;
+       return ret;
+}
+/** Returns the number of keys in this hashtable */
+Hashtable.prototype.size = function() {
+       return this._size;
+}
+
+/** Returns an array with all the values in this hashtable */
+Hashtable.prototype.elements = function() {
+       var ret = new Array();
+       for (var key  in this.data) {
+               ret.push(this.data[key]);
+       }
+       return ret;
+}
+
+/** Returns an array with all the keys in this hashtable */
+Hashtable.prototype.keys = function() {
+       var ret = new Array();
+       for (var key  in this.data) {
+               ret.push(key);
+       }
+       return ret;
+}
+
+/** Returns a string with the keys and values */
+Hashtable.prototype.toString = function() {
+       var ret = "";
+       for(var key in this.data) {
+               ret +="{" + key + "," + value + "}";
+       }
+       return ret;
+}
diff --git a/std/command.js b/std/command.js
new file mode 100644 (file)
index 0000000..7d7bd33
--- /dev/null
@@ -0,0 +1,1003 @@
+/* 
+    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.
+*/
+
+var position_txt_node = null;
+var bitrate_txt_node = null;
+var ALBUMART_URL = "metadata.php?pic=";
+var OUTPUT_ID = "output_";
+
+var DIR_SEPARATOR = "/";
+
+var STATUS_DEFAULT_TIMEOUT = 10;
+
+/* todo: put these in an object, no reason for them to be lying around here */
+var last_update_cmd_id;
+var problem_update_delay = 5;
+var last_update = -1; 
+var pl = new Array();
+var possliderid = -1;
+var volsliderid = -1;
+var playing = new Object();
+playing.id = -1;
+playing.pos = -1;
+playing.length = 0;
+playing.pl_version = 0;
+playing.pl_size = 0;
+playing.size = 0;
+playing.artist = "";
+playing.album = "";
+playing.title = "";
+playing.image = "";    // 
+playing.asin = "";     
+playing.random = 0;    // random on/off
+playing.repeat = 0;    // repeat on/off
+playing.xfade = 0;     // crossfade seconds
+playing.update = false; // dbupdate
+playing.show_node = null; // node that is supposed to have playing status
+playing.is_stream = false; // true if the playing song is a stream
+playing.error = false;
+playing.error_time = 0;
+
+playing.state = "";
+playing.volume = -1;
+playing.TIME_ELAPSED = 0; 
+playing.TIME_REMAINING = 1;
+playing.TIME_END = 2; 
+playing.time_dtype = playing.TIME_ELAPSED;
+
+/* various elements found around the document */
+playing.disp_info = null;
+playing.pp_button = null;
+playing.disp_artist = null;
+playing.disp_title = null; 
+playing.disp_album = null;
+playing.albumart = null;
+
+var last_pl_selected = true;
+
+var output_toggle_id = null;
+
+var pl_overlay_id = -1;
+var pl_overlay_write = null;
+
+var status_bar = null;
+
+var send_command_rm_status = false;
+var pl_entry_clone = null;
+
+var settings_time = 10;
+
+var need_info_arr = null;
+
+var init_failed = false;
+
+var playlist_add_popup = null;
+var playlist_save_popup = null;
+
+var pagination = new Object();
+pagination.max = 0; // max pr. page
+pagination.page = 0; // what page we currently are on
+pagination.pages = 0; // number of pages being advertised
+pagination.need_update = false; // indicates wheter we need an update
+pagination.list = null; // reference to the displaying area
+pagination.container = null; // reference to the container
+
+function init_player() {
+       try {
+               status_bar = new StatusBar();
+
+               if(update_delay) {
+                       if(update_delay<0)
+                               update_delay = 1;
+                       update_delay = update_delay * 1000;
+               }
+               else window.update_delay = 1000;
+
+               possliderid = setup_slider(document.getElementById('posslider'), position_adjust, LANG.POSITION);
+               set_slider_pos(possliderid, 0);
+               
+               volsliderid = setup_slider(document.getElementById('volslider'), volume_adjust, LANG.VOLUME);
+               set_slider_pos(volsliderid, 0);
+
+               playlist_element = document.getElementById('playlist')
+       
+               var pltmp_id = setup_node_move(playlist_element, playlist_move, playlist_get_move_txt);
+               add_move_doubleclick(pltmp_id, playlist_dblclick);
+               set_selection_change_handler(pltmp_id, pl_selection_changed);
+               
+               pl_overlay_id = setup_overlay(playlist_element, new Array(10, 10, 300, 300 ), pl_overlay_open_cb, pl_overlay_close_cb);
+               pl_overlay_write = get_overlay_write_area(pl_overlay_id);
+
+               playing.disp_info =  document.getElementById('disp_info');
+               playing.disp_artist = document.getElementById('disp_artist');
+               playing.disp_title = document.getElementById('disp_title');
+               playing.disp_album = document.getElementById('disp_album');
+               playing.albumart = document.getElementById("albumart");
+               playing.pp_button = document.getElementById("pp_button");
+
+               pagination.list = document.getElementById('pagination_list');
+               pagination.container = document.getElementById('pagination');
+               pagination.max = pagination_max; // nice :S
+
+               var tmp = setting_get("time_dtype");
+
+               if(tmp==null) {
+                       setting_set("time_dtype", playing.time_dtype);
+               }
+               else {
+                       if(tmp==playing.TIME_ELAPSED)
+                               playing.time_dtype = playing.TIME_ELAPSED;
+                       else if(tmp==playing.TIME_REMAINING)
+                               playing.time_dtype = playing.TIME_REMAINING;
+               }
+               
+               xpath_init();
+               buttons_init();
+               setup_keys(); 
+               pagination_init();
+               sidebar_init();
+               plsearch_init();
+               streaming_init();
+
+               if(typeof(window.metadata_init)=='function')
+                       metadata_init();
+               if(typeof(window.recommend_init)=='function')
+                       recommend_init();
+               playlist_init();
+       }
+       catch(e) {
+               init_failed = true;
+               debug(LANG.E_INIT +": " + e.message, true);
+       }
+}
+
+/* arg-list: command to send, command to call when result is return, show status message when working, 
+       don't request status update with this sendcommand, 
+       post data if we should use that, if it should form-urlencoded content type should be set */
+function send_command(command, result_callback, status_msg, nostatus, do_post, form_urlencoded) {
+       if(init_failed)
+               return;
+
+       var http = new XMLHttpRequest(); 
+       var url = "command.php?" + command;
+
+       if(!nostatus) {
+         url+="&plchanges=" + playing.pl_version;
+         if(pagination.max>0) {
+               url+="&pmax=" + pagination.max + "&page=" + pagination.page;
+               //debug("pmax: " + pagination.max + ", page: " + pagination.page);
+         }
+       }
+
+       if(send_command_rm_status) {
+               hide_status_bar();
+               send_command_rm_status = false;
+       }
+
+       http.onreadystatechange = function() {
+         if(http.readyState==4) {
+               var resp = null;
+               if(http.responseText&&(resp = evaluate_json(http.responseText))) {
+                       if(resp['connection']&&resp['connection']=="failed") {
+                               last_update = get_time();
+                               show_status_bar(LANG.E_CONNECT);
+                               send_command_rm_status = true;
+                               try {
+                                       result_callback("failed");
+                               }
+                               catch(e) {}
+                               return;
+                       }
+               }
+               else {
+                       show_status_bar(LANG.E_INVALID_RESPONSE);
+                       send_command_rm_status = true;
+                       last_update = get_time();
+                       try {
+                               result_callback("failed");
+                       }
+                       catch(e) {}
+                       return;
+               }
+
+           if(http.status==200) {
+               var res = resp['result'];
+               var stat = resp["status"];
+               var plchanges = resp["plchanges"];
+               var has_plchanges = plchanges && stat && playing.pl_version != stat.playlist;
+
+               if(res&&result_callback) {
+                       result_callback(res);
+               }
+
+               if(stat) {
+                       current_status_handler(stat, has_plchanges);
+                       last_update = get_time();
+               }
+
+               if(has_plchanges) {
+                       playing.pl_version = stat.playlist;
+                       plchanges_handler3(plchanges, stat.playlistlength);
+                       /* the currently playing song might have changed if it's a stream */
+                       if(playing.is_stream) {
+                               request_song_info();
+                       }
+               }
+
+               /* don't remove if there's no message or a timer */
+               if(status_msg&&!status_bar.timer)
+                       hide_status_bar();
+           }
+           else {
+               if(result_callback) { 
+                       result_callback("server operation failed");
+               }
+               show_status_bar(LANG.NO_RESPONSE); // maybe add a 10 second delay here and reconnect!
+               send_command_rm_status = true;
+           }
+         }
+       }
+
+       if(status_msg) {
+               show_status_bar(status_msg, true);
+       }
+
+
+       if(do_post) {
+               http.open("POST", url);
+               if(form_urlencoded) {
+                       http.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
+               }
+               http.send(do_post);
+       }
+       else {
+               http.open("GET", url);  
+               http.send(null);
+       }
+}
+
+function update_callback(res) {
+       var time = update_delay;
+       if(res=="failed")
+               time = problem_update_delay*update_delay;
+       last_update_cmd_id = setTimeout(need_update, time);
+}
+
+function reschedule_update_now() {
+       clearTimeout(last_update_cmd_id);
+       // make sure it get's run immidiately
+       last_update_cmd_id = setTimeout(need_update, 0, -update_delay);
+}
+
+function need_update(delay) {
+       if(!delay)
+          delay = 0;
+       var now = get_time();
+       if(now>last_update+update_delay+delay-20) { // giving it a little slack
+               send_command("ping", update_callback);
+       }
+       else {
+               delay = last_update+update_delay-now;
+               if(delay<10)
+                       delay = 10;
+                       
+               last_update_cmd_id = setTimeout(need_update, delay);
+       }
+}
+
+/* stop program */
+function handle_fatal_error(txt) {
+       show_status_bar(txt);
+       clearTimeout(last_update_cmd_id);
+}
+
+/* current status handler, info is stats
+ * if has_plchanges it is assumed the data also carries plchanges 
+ * which will handle resizing of the playlist */
+function current_status_handler(info, has_plchanges) {
+       if(!info) {
+               // something is wrong
+               set_slider_pos(possliderid, 0);
+               return;
+       }
+       
+       var tmp = info.playlistlength;
+       if((tmp = parseInt(tmp))>=0) {
+               if(playing.pl_size!=tmp) {
+                       playing.pl_size = tmp;
+                       if(pagination.max>0) {
+                               // make sure size fits inside what we have as number of pages
+                               var s = pagination.max * (pagination.pages);
+                               if(tmp < s-pagination.max || tmp>=s) {
+                                       pagination_update_list(tmp);
+                               }
+                               if(!has_plchanges) {
+                                       playlist_resize(tmp); 
+                               }
+                       }
+                       else if(!has_plchanges) { // if it doesn't carry plchanges we have to do it here
+                               playlist_resize(tmp); 
+                       }
+               }
+       }
+
+       tmp = info.updating_db;
+       if(tmp) {
+               tmp = parseInt(tmp);
+               if(playing.update!=tmp) {
+                       playing.update = tmp;
+                       show_status_bar(LANG.WAIT_UPDATING_DB, true);
+               }
+       }
+       else if(playing.update) {
+               playing.update = false;
+               hide_status_bar();
+       }
+       
+       var state = info["state"];
+       var volume = info[ "volume"];
+       if(volume!=null&&volume!=playing.volume) {
+               set_slider_pos(volsliderid, volume);
+               playing.volume = volume;
+       }
+
+       playing.repeat = info.repeat;
+       playing.random = info.random;
+       playing.xfade = info.xfade;
+
+       if(state!="stop") {
+               
+               var pos = info["time"].split(":");
+               set_slider_pos(possliderid, (pos[0]*100)/pos[1]);
+               playing.length = pos[1];
+
+               /* complex, but seems to be somewhat better in firefox */
+               var tmp = info.bitrate;
+               if(tmp&&playing.bitrate!=tmp) {
+                       var disp_info = playing.disp_info
+                       if(bitrate_txt_node==null) {
+                               bitrate_txt_node = document.createTextNode(LANG.BITRATE);
+                               disp_info.appendChild(bitrate_txt_node);
+                               if(disp_info.normalize)
+                                       disp_info.normalize();
+                               bitrate_txt_node = disp_info.firstChild.splitText(LANG.BITRATE.length);
+                       }
+                       var rep = document.createTextNode(tmp);
+                       disp_info.replaceChild(rep, bitrate_txt_node);
+                       bitrate_txt_node = rep;
+                       playing.bitrate = tmp;
+               }
+
+               var postxt = get_slider_txt(possliderid);
+               if(postxt) {
+                       if(position_txt_node==null) {
+                               position_txt_node = postxt.firstChild.splitText(LANG.POSITION.length);
+                       }
+                       var rep = create_txt(format_playing_time(pos[0], pos[1]));
+                       postxt.replaceChild(rep, position_txt_node);
+                       position_txt_node = rep; 
+               }
+       }
+       else if(playing.state!="stop") { // state == stop and last state wasn't stop
+               set_slider_pos(possliderid, 0);
+               var disp_info = playing.disp_info;
+               var rep = document.createTextNode("0");
+               var postxt = get_slider_txt(possliderid);
+               if(bitrate_txt_node&&bitrate_txt_node!=null) {
+                       disp_info.replaceChild(rep, bitrate_txt_node);
+                       bitrate_txt_node = rep;
+               }
+               playing.bitrate = 0;
+               rep = document.createTextNode("");
+               if(position_txt_node&&position_txt_node!=null) {
+                       postxt.replaceChild(rep, position_txt_node);
+                       position_txt_node = rep; 
+               }
+       }
+       if(state!=playing.state) {
+               playing.state = state;
+               var bt = playing.pp_button;
+               if(state=="play") {
+                       bt.src = IMAGE.BUTTON_PAUSE;
+                       if(typeof(window.streaming_try_autoplay)=='function')
+                               streaming_try_autoplay();
+               }
+               else {
+                       bt.src = IMAGE.BUTTON_PLAY;
+                       if(typeof(window.streaming_try_autostop)=='function')
+                               streaming_try_autostop();
+               }
+       }
+       
+       if(typeof info.error != 'undefined' || playing.error) {
+               if(playing.error && get_time() - 10000 > playing.error_time) {
+                       playing.error = false;
+                       hide_status_bar();
+                       send_clear_error();
+               }
+               else if(typeof info.error != 'undefined' && playing.error != info.error) {
+                       playing.error = info.error;
+                       playing.error_time = get_time();
+                       show_status_bar("MPD error: " + playing.error);
+               }
+       }
+
+       var c_play = info.songid;
+       if(typeof(c_play)=='undefined') {
+               playing.id = -1;
+               playing.pos = -1;
+               request_song_info();
+               select_playing_song();
+       }
+       else if(c_play!=playing.id) {
+               playing.id = c_play;
+               playing.pos = parseInt(info.song);
+               select_playing_song();
+               if(pagination_is_following()) {
+                       playlist_scroll_to_playing();
+               }
+               /* this is last because it needs the id */
+               request_song_info();
+       }
+       else if(playing.pos!=info.song) {
+               playing.pos = parseInt(info.song);
+       }
+}
+
+function format_playing_time(pos, total) {
+       if(playing.time_dtype==playing.TIME_REMAINING) {
+               return convert_minsec(pos-total) + "/" + convert_minsec(total);
+       }
+       else { // dtype == playing.TIME_ELAPSED || something wrong
+               return convert_minsec(pos) + "/" + convert_minsec(total);
+       }
+}
+
+function request_song_info() {
+       /* clean up */
+       if(playing.id<0) {
+               remove_children(playing.disp_artist);
+               remove_children(playing.disp_title);
+               remove_children(playing.disp_album);
+               if(playing.albumart)
+                       remove_children(playing.albumart);
+               playing.artist = "";
+               playing.title = "";
+               playing.album = "";
+               playing.image = "";
+               playing.asin = "";
+               playing.length = "";
+               playing.is_stream = false;
+               set_playing_title();
+       }
+       else {
+               /* or update */
+               send_command("currentsong", update_current_song, false, true);
+       }
+}
+
+function update_current_song(info) {
+       var artist = info[ "Artist"];
+       var title = info["Title"];
+       var album = info[ "Album"];
+       var a = playing.disp_artist;
+       var t = playing.disp_title;
+       var alb = playing.disp_album;
+       var new_thumb = false;
+
+       if(typeof(title)=='undefined')
+               title = "";
+       if(typeof(album)=='undefined')
+               album = "";
+       if(typeof(artist)=='undefined')
+               artist = "";
+
+       if(artist!=playing.artist) {
+               playing.artist = artist;
+               new_thumb = true;
+               remove_children(a);
+               a.appendChild(create_txt(artist));
+       }
+       if(playing.album != album) {
+               playing.album = album;
+               new_thumb = true;
+               remove_children(alb);
+               alb.appendChild(create_txt(album));
+       }
+
+       if(typeof(info['file'])!='undefined') {
+               var f = info['file'];
+               if(f&&f.indexOf("http://")==0)
+                       playing.is_stream = true;
+               else playing.is_stream = false;
+       }
+
+       remove_children(t);
+       playing.title = title;
+
+       if(title==null||title=="") {
+               title = info["file"];
+               if(title)
+                       title = title.substring(title.lastIndexOf(DIR_SEPARATOR)+1);
+       }
+       t.appendChild(create_txt(title));
+
+       set_playing_title(artist, title);
+
+       if(new_thumb&&typeof(window.request_thumbnail) == 'function') {
+               setTimeout(request_thumbnail, 1);
+       }
+}
+
+function set_playing_title(artist, title) {
+       if(typeof(artist)=='undefined'||artist==null)
+               artist = "";
+       if(typeof(title)=='undefined'||title==null)
+               title = "";
+       
+       var wt = "Pitchfork MPD Client";
+       if(artist.length||title.length) {
+               wt = artist;
+               if(title.length)
+                       wt += " - " + title;
+       }
+       document.title = wt;
+}
+
+function volume_adjust(vol) {
+       send_command("volume=" + parseInt(vol));
+}
+
+function position_adjust(pos) {
+       send_command("position=" + parseInt((pos* parseInt(playing.length))/100) + "&id=" + playing.id);
+}
+
+function convert_minsec(sec) {
+       var min = parseInt(sec/60);
+       var s = Math.abs(sec%60);
+       return (sec<0&&min==0?"-" + min:min)  + ":" + (s<10?"0" + s:s);
+}
+
+function buttons_init() {
+       
+       /* player control */
+       var elem = document.getElementById('pp_button');
+       elem.src = IMAGE.BUTTON_PLAY;
+       add_listener(elem, "click", send_play_pause);
+       if(window.stop_button) {
+               elem = document.getElementById('stop_button');
+               elem.style.display = "";
+               elem.src = IMAGE.BUTTON_STOP;
+               add_listener(elem, "click", send_stop_cmd);
+               elem.parentNode.style.marginLeft = "-15px";
+       }
+
+       elem = document.getElementById("next_button");
+       elem.src = IMAGE.BUTTON_NEXT;
+       add_listener(elem, "click", send_next_song);
+       elem = document.getElementById("previous_button");
+       elem.src = IMAGE.BUTTON_PREVIOUS;
+       add_listener(elem, "click", send_previous_song);
+
+       /* left menu buttons */
+       elem = document.getElementById("open_directory_button");
+       elem.src = IMAGE.MENU_ITEM_DIRECTORY;
+       add_listener(elem, "click", open_pl_overlay);
+       elem = document.getElementById("crop_items_button");
+       elem.src = IMAGE.MENU_ITEM_CROP;
+       add_listener(elem, "click", remove_songs_event);
+       elem = document.getElementById("remove_items_button");
+       elem.src = IMAGE.MENU_ITEM_REMOVE;
+       add_listener(elem, "click", remove_songs_event);
+
+       /* server settings */
+       elem = document.getElementById("settings_header");
+       add_listener(elem, "click", open_close_settings);
+       add_listener(elem, "mousedown", stop_event);
+
+       /* playlist */
+       elem = document.getElementById("playlist_add");
+       add_listener(elem, "click", playlist_add_button);
+       elem = document.getElementById("playlist_save");
+       add_listener(elem, "click", playlist_save_button);
+
+       /* status bar */
+       elem = document.getElementById("status_bar_img");
+       elem.src = IMAGE.WORKING;
+
+       /* streaming if applicable */
+       elem = document.getElementById("streaming_open");
+       if(elem) {
+               add_listener(elem, "click", streaming_open);
+       }
+
+       /* time display */
+       elem = get_slider_txt(possliderid);
+       if(elem) {
+               add_listener(elem, "click", change_pos_dtype);
+       }
+
+       // pagination
+       elem = document.getElementById("pagination");
+       if(elem) {
+               add_listener(elem, "click", pagination_change_page);
+               add_listener(elem, "mousemove", pagination_scroll_view);
+               add_listener(elem, "mouseout", pagination_scroll_stop);
+       }
+       elem = document.getElementById("pagination_jump_current");
+       if(elem) {
+               elem.src = IMAGE.JUMP_CURRENT;
+               add_listener(elem, "click", playlist_scroll_to_playing);
+               elem.title = LANG.JUMP_CURRENT;
+       }
+       elem = document.getElementById("pagination_follow_current");
+       if(elem) {
+               add_listener(elem, "click", pagination_toggle_following);
+       }
+
+       elem = document.getElementById("playlist_search_btn");
+       if(elem) {
+               add_listener(elem, "click", plsearch_open);
+       }
+
+       // set it to nothing selected
+       pl_selection_changed(false);
+}
+
+function send_play_pause(e) {
+       stop_event(e);
+       var act = "toggle";
+       if(playing.state=="stop") {
+               act = "play";
+               if(playing.id>=0)
+                       act+="&id=" + playing.id;
+       }
+       send_command("act=" + act);
+}
+function send_stop_cmd(e) {
+       stop_event(e);
+       send_command("act=stop");
+}
+function send_next_song(e) {
+       stop_event(e);
+       send_command("act=next");
+}
+function send_previous_song(e) {
+       stop_event(e);
+       send_command("act=previous");
+}
+
+function send_update_db_cmd(e) {
+       stop_event(e);
+       send_command("updatedb");
+}
+
+function send_clear_error() {
+       send_command("clearerror", false, false, true);
+}
+
+function remove_songs_event(e) {
+       var inv = 'crop_items_button'==e.target.id;
+       var sel = get_pl_selection_range(inv);
+       if(!inv) {
+               /* nothing selected if we just removed it, 
+                * or at least in theory */
+               pl_selection_changed(false);
+       }
+       if(sel.length==0) {
+               return;
+       }
+       send_command("remove=" + encodeURIComponent(sel), remove_songs_cb, LANG.WAIT_REMOVING);
+}
+
+function remove_songs_cb(response) {
+       if(response!="ok") {
+               show_status_bar(LANG.E_REMOVE);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+}
+
+function open_close_settings(e) {
+       var sc = document.getElementById('settings_container');
+       /* close */
+       if(sc.style.display == "block") { /* not perfect but I think there's enough vars at the top */
+               sc.firstChild.style.display = "none";
+               remove_listener(sc, "mousedown", stop_event);
+               remove_listener(sc, "click", settings_click_handler);
+               setTimeout(open_close_settings_timer, settings_time, sc, false, new Array(0, 200));
+       }
+       else { 
+       /* open */
+               var dst_height = sc.scrollHeight; // || innerHeight
+               sc.style.height = "50px";
+               sc.style.overflow = "hidden";
+               remove_children(sc.firstChild);
+               sc.firstChild.style.display = "none";
+               sc.firstChild.appendChild(document.createTextNode("Loading.."));
+               sc.style.display = "block"; 
+               add_listener(sc, "mousedown", stop_event);
+               add_listener(sc, "click", settings_click_handler);
+               setTimeout(open_close_settings_timer, settings_time, sc, true, new Array(0, 200));
+               send_command("outputs", open_settings_cb);
+       }
+}
+
+function open_close_settings_timer(sc, isOpening, heights) {
+       var ad = 50;
+       if(isOpening)
+               heights[0] += ad;
+       else heights[1] -=ad;
+       
+       if(heights[0]<heights[1]) {
+               sc.style.height = (isOpening?heights[0]:heights[1]) + "px";
+               setTimeout(open_close_settings_timer, settings_time, sc, isOpening, heights);
+       }
+       else {
+               if(isOpening) {
+                       //sc.style.overflow = "auto";
+                       sc.firstChild.style.display = "block";
+                       sc.style.height = heights[1] + "px";
+               }
+               else {
+                       sc.style.display = "none";
+                       sc.style.height = heights[0] + "px";
+               }
+       }
+
+}
+
+function create_settings_status_image(stat) {
+       var img = create_node("img");
+       img.className = "server_settings";
+       if(stat==1||stat=="1") {
+               img.src = IMAGE.SERVER_SETTINGS_ENABLED;
+       }
+       else {
+               img.src = IMAGE.SERVER_SETTINGS_DISABLED;
+       }
+       return img;
+}
+
+function open_settings_cb(response) {
+       var txt = document.getElementById('settings_content');
+       remove_children(txt);
+       var img = create_node("img");
+       img.className = "server_settings";
+
+       function add_entry(id, stat, text, no_img) {
+               var span = create_node("span", id);
+               span.className = "server_settings";
+               if(!no_img) {
+                       var im = create_settings_status_image(stat);
+                       im.id = id + "_img";
+                       span.appendChild(im);
+               }
+               span.appendChild(create_txt(" " + text));
+               txt.appendChild(span);
+               txt.appendChild(create_node("br"));
+               return span;
+       }
+
+       add_entry("repeat_toggle", playing.repeat, LANG.REPEAT);
+       add_entry("random_toggle", playing.random, LANG.RANDOM);
+       var xfade = add_entry("xfade_entry", playing.xfade, LANG.XFADE, true);
+       var xe = create_node("img");
+       xe.name = "left";
+       xe.className = "server_settings";
+       xe.src = IMAGE.SERVER_SETTINGS_XFADE_DOWN;
+       xfade.appendChild(xe);
+       var i_right = xe.cloneNode(true);
+       i_right.name = "right";
+       i_right.src = IMAGE.SERVER_SETTINGS_XFADE_UP;
+       xe = create_node("span", "xfade_adjust_txt", " " + playing.xfade + " ");
+       xfade.appendChild(xe);
+       xfade.appendChild(i_right);
+
+       var tmp = create_node("hr"); 
+       tmp.className = "server_settings";
+       txt.appendChild(tmp);
+       txt.appendChild(create_txt("Outputs:"));
+       txt.appendChild(create_node("br"));
+       try {
+               var outputs = response['outputs'];
+               for(var i in outputs) {
+                       var id = outputs[i]["outputid"];
+                       var enabled = outputs[i]["outputenabled"];
+                       var s = add_entry(OUTPUT_ID + id, enabled, outputs[i]["outputname"]);
+                       s.setAttribute("outputenabled", enabled);
+               }
+       }
+       catch(e) {
+               debug(e.message);
+               txt.appendChild(create_txt(LANG.E_NO_OUTPUTS));
+       }
+}
+
+function settings_click_handler(e) {
+       for(var n = e.target; n.parentNode; n=n.parentNode) {
+               if(n.id) {
+                       if(n.id.indexOf(OUTPUT_ID)==0&&n.id.indexOf("img")<0) {
+                               toggle_output(n);
+                               break;
+                       }
+                       else if(n.id=="repeat_toggle") {
+                               toggle_repeat(n);
+                               break;
+                       }
+                       else if(n.id=="random_toggle") {
+                               toggle_random(e);
+                               break;
+                       }
+                       else if(n.id=="xfade_entry") {
+                               xfade_adjust(n, e);
+                       }
+                       else if(n.id=="settings_container") {
+                               break;
+                       }
+               }
+       }
+       stop_event(e);
+}
+
+function toggle_repeat(e) {
+       send_command("repeat=" + (parseInt(playing.repeat)==0?1:0), toggle_repeat_cb);
+}
+function toggle_random(e) {
+       send_command("random=" + (parseInt(playing.random)==0?1:0), toggle_random_cb);
+}
+function toggle_output(e) {
+       target = e;
+       output_toggle_id = target.id;
+       id = target.id.substring(OUTPUT_ID.length);
+       var cmd = "output_";
+       if(target.getAttribute("outputenabled")==1)
+               cmd+="d";
+       else cmd+="e";
+       cmd+="=" + id;
+       send_command(cmd, output_change_cb);
+}
+
+function xfade_adjust(node, ev) {
+       if(!ev.target.name) {
+               return;
+       }
+       var name = ev.target.name;
+       if(name!="left"&&name!="right") {
+               return;
+       }
+       var xfade= parseInt(playing.xfade) + ("left"==name?-1:+1);
+       if(xfade<0)
+               xfade=0;
+       send_command("xfade=" + xfade);
+       var x = document.getElementById("xfade_adjust_txt");
+       if(x.firstChild) {
+               x.firstChild.nodeValue = " " + xfade + " ";
+       }
+}
+
+function toggle_repeat_cb(response) {
+       var n = document.getElementById("repeat_toggle_img");
+       var img = create_settings_status_image(response);
+       replace_node(img, n);
+       img.id = "repeat_toggle_img";
+}
+function toggle_random_cb(response) {
+       var n = document.getElementById("random_toggle_img");
+       var img = create_settings_status_image(response);
+       replace_node(img, n);
+       img.id = "random_toggle_img";
+}
+function output_change_cb(response) {
+       if(output_toggle_id==null) 
+               return;
+       var n = document.getElementById(output_toggle_id);
+       if(!n) 
+               return;
+       var o_img = document.getElementById(output_toggle_id + "_img");
+       n.setAttribute("outputenabled", response);
+       var img = create_settings_status_image(response);
+       img.id = o_img.id;
+       replace_node(img, o_img);
+       output_toggle_id = null;
+}
+
+
+function send_play_pos(pos) {
+       send_command("act=play&pos=" + pos);
+}
+
+function open_pl_overlay(e) {
+       if(open_overlay_idx<0) {
+               open_overlay_fixed(pl_overlay_id);
+       }
+       else {
+               close_overlay(pl_overlay_id);
+       }
+}
+
+function StatusBar(txt) {
+       this.txt = document.getElementById('status_bar_txt');
+       this.img = document.getElementById('status_bar_img');
+       this.main = document.getElementById('status_bar');
+       this.timer = false;
+}
+
+/* status bar (could be put in toolkit though */
+function show_status_bar(txt, working) {
+       txt = create_txt(txt);
+       remove_children(status_bar.txt);
+       status_bar.txt.appendChild(txt);
+       if(working) {
+               status_bar.img.setAttribute("working", "yeah");
+       }
+       else {
+               if(status_bar.img.hasAttribute("working"))
+                       status_bar.img.removeAttribute("working");
+       }
+       status_bar.main.style.display = "block";
+       /* to prevent it from disappearing again if it is showing */
+       if(status_bar.timer) {
+               clearTimeout(status_bar.timer);
+               status_bar.timer = false;
+       }
+}
+
+/* hides status-bar after optional number of seconds */
+function hide_status_bar(time) {
+       if(typeof(time)!='undefined'&&time&&time>0) {
+               status_bar.timer = setTimeout(hide_status_bar, time*1000, false);
+       }
+       else {
+               remove_children(status_bar.txt);
+               if(browser_is_opera()) {
+                       opera_quirk_set_display_none(status_bar.main);
+               }
+               else {
+                       status_bar.main.style.display = "none";
+               }
+               status_bar.timer = false;
+       }
+}
+
+function setup_keys() {
+       keyboard_init(); 
+       keyboard_register_listener(send_play_pause, "k", KEYBOARD_NO_KEY, true);
+       keyboard_register_listener(send_previous_song, "j", KEYBOARD_NO_KEY, true);
+       keyboard_register_listener(send_next_song, "l", KEYBOARD_NO_KEY, true);
+       keyboard_register_listener(quickadd_focus, "s", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true);
+       /* for browsers where ctrl+shift does something else */
+       keyboard_register_listener(quickadd_focus, "s", KEYBOARD_CTRL_KEY|KEYBOARD_ALT_KEY, true);
+       keyboard_register_listener(playlist_scroll_to_playing, " " , KEYBOARD_NO_KEY, true);
+
+       var qa = document.getElementById('quickadd');
+       qa.setAttribute("autocomplete", "off");
+       add_listener(qa, "keydown", quickadd_keydown_handler); // stop it from getting to the keylisteners!
+       add_listener(qa, "keyup", quickadd_keyup_handler);
+       add_listener(qa, "focus", quickadd_focus);
+       add_listener(qa, "blur", quickadd_blur);
+       qa.title = LANG.QUICK_ADD + " [Ctrl+Shift+S]";
+}
+
+function change_pos_dtype() {
+       playing.time_dtype++;
+       if(playing.time_dtype>=playing.TIME_END) {
+               playing.time_dtype = 0; 
+       }
+       setting_set("time_dtype", playing.time_dtype);
+}
+
diff --git a/std/keyboard.js b/std/keyboard.js
new file mode 100644 (file)
index 0000000..b24c8de
--- /dev/null
@@ -0,0 +1,112 @@
+/* 
+    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.
+*/
+
+var RETURN_KEY_CODE = 13; // enter...
+
+var keyboard_listeners;
+var keyboard_elem_count = 0;
+
+var KEYBOARD_NO_KEY = 0;
+var KEYBOARD_CTRL_KEY = 1;
+var KEYBOARD_ALT_KEY = 1 << 1;
+var KEYBOARD_META_KEY = 1 << 2;
+var KEYBOARD_SHIFT_KEY = 1 << 3;
+
+var KEYBOARD_MATCH_ALL = "";
+
+
+function KeyListener(callback_function, match_char, extra_keys, active) {
+       this.callback = callback_function;
+       this.matchChar = match_char; 
+       this.extra_keys = extra_keys;
+       this.active = active;
+}
+
+function keyboard_init() {
+       document.addEventListener("keydown", keyboard_input_handler_cb, false);
+       keyboard_listeners = new HashMap();
+       if(document.body.focus)
+               document.body.focus();
+}
+
+function keyboard_register_listener(callback_func, match_char, extra_keys, active) {
+       var kid = keyboard_elem_count++;
+
+       if(match_char==KEYBOARD_MATCH_ALL) {
+               match_char = -1;
+               keyboard_listeners.insert(kid, new KeyListener(callback_func, match_char, extra_keys, active));
+       }
+       else {
+               match_char = match_char.toUpperCase().charCodeAt(0);
+               keyboard_listeners.put(kid, new KeyListener(callback_func, match_char, extra_keys, active));
+       }
+
+       return kid;
+}
+
+function keyboard_set_active(id, active) {
+       try {
+               keyboard_listeners.get(id).active = active;
+       }
+       catch(e) {
+               debug("setting kb " + id + " to " + active + " failed");
+       }
+}
+
+function keyboard_remove_listener(key) {
+       keyboard_listeners.remove(key);
+}
+
+function keyboard_input_handler_cb(ev) {
+       var key = -1;
+
+       //debug("got a key: " + (ev.shiftKey?"shift ":"") + (ev.ctrlKey?"ctrl ":"") + (ev.altKey?"alt ":"") + (ev.metaKey?"meta ":""));
+       
+       if(ev.which&&ev.which>0)
+               key = ev.which;
+       else key = ev.keyCode; // hmm....
+
+       // this is ctrl+some key, yank it up by 64
+       if(ev.ctrlKey&&key>0&&key<27) {
+               key+=64;
+       }
+
+       // just match a-z and some special chars (search: ascii key table)
+       if(key<32||key>90)
+               return true;
+       
+       var num = KEYBOARD_NO_KEY;
+       if(ev.ctrlKey)
+               num += KEYBOARD_CTRL_KEY;
+       if(ev.shiftKey)
+               num += KEYBOARD_SHIFT_KEY
+       if(ev.altKey)
+               num += KEYBOARD_ALT_KEY;
+       if(ev.metaKey)
+               num += KEYBOARD_META_KEY;
+       var it = keyboard_listeners.iterator();
+       var l = null;
+       while((l = it.next())!=null) {
+               if(l.active && num == l.extra_keys && (l.matchChar == key||l.matchChar<0)) {
+                       l.callback(ev);
+                       stop_event(ev);
+                       return false;
+               }
+       }
+       return true;
+}
diff --git a/std/metadata.js b/std/metadata.js
new file mode 100644 (file)
index 0000000..36cfd1f
--- /dev/null
@@ -0,0 +1,514 @@
+/* 
+    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.
+*/
+
+var ALBUMART_NO_COVER = "../images/gmpc-no-cover.png";
+var ALBUMART_NO_COVER_THUMB = "../images/gmpc-no-cover-thumb.png";
+
+function metadata_open_default() {
+       sidebar_open("metadata");
+       metadata_open_lyrics(); // default opening is lyrics
+}
+
+function metadata_init() {
+
+       var elem = document.getElementById('metadata_open');
+       if(elem)
+               add_listener(elem, "click", metadata_open_default);
+       elem = document.getElementById("metadata_close");
+       if(elem)
+               add_listener(elem, "click", sidebar_close);
+
+       elem = document.getElementById("metadata_open_lyrics");
+       if(elem)
+               add_listener(elem, "click", metadata_open_lyrics);
+       elem = document.getElementById('metadata_open_review');
+       if(elem)
+               add_listener(elem, "click", metadata_open_review);
+       elem = document.getElementById('metadata_open_description');
+       if(elem)
+               add_listener(elem, "click", metadata_open_description);
+       if(sidebar.display)
+               add_listener(sidebar.display, "click", metadata_lyrics_refetch);
+       elem = document.getElementById("recommendation_open");
+       if(elem)
+               add_listener(elem, "click", recommend_open);
+}
+
+function parse_html_tags(txt) {
+       txt = txt.replace(/&lt;i&gt;/gi, "<i>");
+       txt = txt.replace(/&lt;\/i&gt;/gi, "</i>");
+       txt = txt.replace(/\n/g, "<br/>\n");
+       txt = txt.replace(/&lt;p&gt;/gi, "<br/><br/>\n");
+       txt = txt.replace(/&lt;\/p&gt;/gi, "");
+       return txt;
+}
+
+function metadata_set_content(content) {
+       /* don't display the content if right view is not open */
+       if(sidebar.open_view=="metadata") 
+               sidebar.set_content(content);
+}
+
+function metadata_open_lyrics(e, force) {
+       sidebar.say_loading();
+       sidebar.last_metadata_request = "lyrics";
+       if(playing.title&&playing.artist&&playing.title.length>0&&playing.artist.length>0) {
+               var http = new XMLHttpRequest();
+               http.open("GET", "metadata.php?lyric" + (force?"&force":""), true);
+               http.onreadystatechange = function() {
+                       if(http.readyState==4&&sidebar.last_metadata_request=="lyrics") {
+                               var lyrics = create_fragment();
+                               if(http.responseText=="nocachedir") {
+                                       lyrics.appendChild(create_txt(LANG.E_MISSING_CACHE));
+                               }
+                                       
+                               if(http.responseXML) {
+                                       var result = get_tag(http.responseXML, "result");
+                                       if(result=="failed") {
+                                               lyrics.appendChild(create_txt(LANG.E_LYRICS_FAILURE));
+                                       }
+                                       else if(result=="notfound") {
+                                               lyrics.appendChild(create_txt(LANG.E_LYRICS_NOT_FOUND));
+                                               var url = get_tag(http.responseXML, "url");
+                                               if(url) {
+                                                       lyrics.appendChild(metadata_build_lyricwiki_url(url, LANG.ADD));
+                                               }
+                                       }
+                                       else {
+                                               var refetch = create_node("p");
+                                               refetch.appendChild(create_txt("["+LANG.REFETCH+"]"));
+                                               refetch.className = "fakelink";
+                                               refetch.id = "lyrics_refetch_button";
+                                               lyrics.appendChild(refetch);
+                                               var title = create_node("span");
+                                               title.style.fontSize = "larger";
+                                               title.style.marginRight = "60px";
+                                               title.appendChild(create_txt(get_tag(http.responseXML, "title")));
+                                               var artist = create_node("span");
+                                               artist.style.fontSize = "100%";
+                                               artist.appendChild(create_txt(get_tag(http.responseXML, "artist")));
+
+                                               lyrics.appendChild(title);
+                                               lyrics.appendChild(create_node("br"));
+                                               lyrics.appendChild(artist);
+                                               lyrics.appendChild(create_node("br"));
+                                               lyrics.appendChild(create_node("br"));
+                                               
+                                               var song = get_tag(http.responseXML, "lyric");
+                                               if(song&&song.length) {
+                                                       add_string_with_br(lyrics, song);
+                                               }
+                                               else {
+                                                       lyrics.appendChild(create_txt(LANG.E_MISSING_LYRICS));
+                                               }
+                                               var url = get_tag(http.responseXML, "url");
+                                               if(url) {
+                                                       lyrics.appendChild(metadata_build_lyricwiki_url(url, LANG.EDIT));
+                                               }
+                                       }
+                               }
+                               else {
+                                       lyrics.appendChild(create_txt(LANG.E_COMM_PROBLEM));
+                               }
+                               metadata_set_content(lyrics);
+                       }
+               }
+               http.send(null);
+       }
+       else {
+               metadata_set_content(create_txt(LANG.E_MISSING_AS_NAME));
+       }
+}
+
+function metadata_build_lyricwiki_url(url, txt) {
+       var a = create_node("a");
+       a.href = url.replace(/&amp;/i, "&");
+       a.title = sprintf(LANG.LYRICWIKI_LYRIC, txt);
+       a.target = "_new";
+       a.appendChild(create_txt("[" + txt + "]"));
+       a.style.display = "block";
+       a.style.marginTop = "10px";
+       return a;
+}
+
+function metadata_lyrics_refetch(e) {
+       if(e&&e.target&&e.target.id=="lyrics_refetch_button") {
+               stop_event(e);
+               metadata_open_lyrics(e, true);
+       }
+}
+
+function metadata_open_review() {
+       request_review_desc(0);
+}
+function metadata_open_description() {
+       request_review_desc(1);
+}
+
+function request_review_desc(type) {
+       sidebar.say_loading();
+       sidebar.last_metadata_request = "review";
+       if(playing.album&&playing.artist&&playing.album.length>0&&playing.artist.length>0) {
+               var http = new XMLHttpRequest()
+               var album = playing.album;
+               var artist = playing.artist;
+               http.open("GET", "metadata.php?review&artist=" + encodeURIComponent(playing.artist) + 
+                       "&album=" + encodeURIComponent(playing.album), true);
+               http.onreadystatechange = function() {
+                       if(http.readyState==4&&sidebar.last_metadata_request=="review") {
+                               if(get_tag(http.responseXML, "result")) {
+                                       metadata_set_content(LANG.E_GET_INFO);
+                                       return;
+                               }
+
+                               var content = create_fragment();
+                               var tmp = "";
+                               var stuff = "";
+                               if(type==0) {
+                                       tmp = LANG.ALBUM_REVIEW;
+                                       stuff = get_tag(http.responseXML, "review");
+                               }
+                               else if(type==1) {
+                                       tmp = LANG.ALBUM_DESC;
+                                       stuff = get_tag(http.responseXML, "desc");
+                               }
+                               if(stuff) {
+                                       if(!playing.asin) {
+                                               var asin = get_tag(http.responseXML, "asin");
+                                               if(asin)
+                                                       playing.asin = asin; 
+                                       }
+                                       tmp+=sprintf(LANG.ALBUM_AA_NAME, album, artist);
+                                       tmp = create_node("span", null, tmp);
+                                       tmp.style.fontSize = "larger";
+                                       content.appendChild(tmp);
+                                       content.appendChild(create_node("br"));
+                                       content.appendChild(create_node("br"));
+                                       tmp = create_node("span");
+                                       tmp.innerHTML = parse_html_tags(stuff);
+                                       content.appendChild(tmp);
+                                       tmp = create_node("a");
+                                       tmp.appendChild(create_txt(LANG.NT_AMAZON));
+                                       if(build_amazon_link(tmp)) {
+                                               content.appendChild(create_node("br"));
+                                               content.appendChild(create_node("br"));
+                                               content.appendChild(tmp);
+                                       }
+                               }
+                               else {
+                                       content.appendChild(create_txt(sprintf(LANG.E_NOT_FOUND, tmp.toLowerCase())));
+                               }
+                               metadata_set_content(content);
+                       }
+               }
+               http.send(null);
+       }
+       else {
+               metadata_set_content(create_txt(LANG.E_MISSING_AA_NAME));
+       }
+}
+
+
+/* album art */
+function request_thumbnail() {
+       var albumart = document.getElementById("albumart");
+       var rartist = playing.artist;
+       var ralbum = playing.album;
+
+       remove_children(albumart);
+       if(playing.album&&playing.artist&&ralbum.length>0&&rartist.length>0) {
+               var http = new XMLHttpRequest()
+               http.open("GET", "metadata.php?cover&artist=" + encodeURIComponent(rartist) + 
+                       "&album=" + encodeURIComponent(ralbum), true);
+               http.onreadystatechange = function() {
+                       if(http.readyState==4) {
+                       try {
+                               if(http.responseText=="nocachedir") {
+                                       debug(LANG.E_MISSING_CACHE); // TODO
+                                       return;
+                               }
+                               if(get_tag(http.responseXML, "result")) {
+                                       // something's not okay.... TODO
+                                       return;
+                               }
+
+                               /* test if we're still wanted */
+                               if(rartist != playing.artist || ralbum != playing.album)
+                                       return;
+
+                               var thumb = get_tag(http.responseXML, "thumbnail");
+                               var url = thumb;
+                               
+                               if(!url) {
+                                       url = ALBUMART_NO_COVER_THUMB;
+                               }
+                               else {
+                                       url = ALBUMART_URL + encodeURIComponent(thumb);
+                               }
+
+                               playing.image = get_tag(http.responseXML, "image");
+
+                               var img = create_node("img");
+                               img.src = url;
+                               add_listener(img, "click", show_big_albumart);
+                               img.className = "thumbnailart";
+                               if(albumart.hasChildNodes()) {
+                                       /* probably just one, but... */
+                                       var imgs = albumart.getElementsByTagName("img");
+                                       for(var i=0; i<imgs.length; i++) {
+                                               remove_listener(imgs[i], "click", show_big_albumart);
+                                       }
+                                       remove_children(albumart);
+                               }
+                               albumart.appendChild(img);
+
+                               var asin = get_tag(http.responseXML, "asin");
+                               if(asin)
+                                       playing.asin = asin;
+                               else playing.asin = false;
+
+                               if(albumart_is_open())
+                                       show_big_albumart();
+
+                       }
+                       catch(e) {
+                               //debug("request_thumbmail error: " + e.message);
+                       }
+                       }
+               }
+               http.send(null);
+       }
+}
+
+function show_big_albumart() {
+       
+       var div = document.getElementById("albumart_show");
+       var close = false;
+       if(!div) {
+               div = create_node("div", "albumart_show");
+               div.className = "big_albumart";
+               document.body.appendChild(div);
+               close = create_node("p");
+               close.style.color = "white";
+               close.style.position = "absolute";
+               close.style.right = "5px";
+               close.style.bottom = "0px";
+               close.style.margin = "0px 5px 5px 0px";
+               close.className = "fakelink";
+               close.appendChild(create_txt("[" + LANG.CLOSE + "]"));
+       }
+
+       /* if it hadn't been for opera we could have used the old one */
+       var oimg = document.getElementById("albumart_img"); 
+       var img = create_node("img", "albumart_img");
+       img.className = "big_albumart";
+       img.style.display = "none";
+       if(oimg)
+               replace_node(img, oimg);
+       else 
+               div.appendChild(img);
+
+       add_listener(img, "load", albumart_loaded);
+
+       var node = document.getElementById("albumart_txt");
+       if(!node) {
+               node = create_node("p", "albumart_txt");
+               div.appendChild(node);
+       }
+       remove_children(node);
+       node.appendChild(create_txt(LANG.WAIT_LOADING));
+
+       add_listener(div, "click", albumart_big_remove);
+       div.style.display = "";
+       var url = "";
+       if(playing.image&&playing.image.length)
+               url = ALBUMART_URL + encodeURIComponent(playing.image);
+       else url = ALBUMART_NO_COVER;
+                       
+       if(close)
+               div.appendChild(close);
+
+       img.src = url;
+
+}
+
+function albumart_is_open() {
+       var aa = document.getElementById("albumart_show");
+       if(aa&&aa.style.display != "none")
+               return true;
+       return false;
+}
+
+function albumart_big_remove() {
+       var aa = document.getElementById("albumart_show");
+       var img = document.getElementById("albumart_img");
+       img.style.display = "none";
+       remove_listener(aa, "click", albumart_big_remove);
+       aa.style.display = "none";
+       aa.style.height = "";
+       aa.style.width = "";
+}
+
+function albumart_loaded() {
+       var img = document.getElementById("albumart_img");
+       var disp = document.getElementById("albumart_show");
+       var txt = document.getElementById("albumart_txt");
+       remove_listener(img, "load", albumart_loaded);
+       img.style.opacity = "0.1";
+       albumart_resize(disp, img);
+       adjust_opacity_timer(img, 0.0, 1.0);
+       remove_children(txt);
+       if(playing.asin&&playing.asin.length>0) {
+               var a = create_node("a");
+               build_amazon_link(a);
+               a.appendChild(create_txt(LANG.NT_AMAZON));
+               a.style.color = "white";
+               a.style.textTransform = "none";
+               a.title = LANG.ALBUM_AMAZON;
+               txt.appendChild(a);
+       }
+}
+
+function albumart_resize(disp, img) {
+       var width = 500; 
+       var height = 500;
+       img.style.visibility = "hidden";
+       img.style.display = "inline";
+       var got_real = true;
+       if(img.height)
+               height = img.height;
+       else got_real = false;
+       if(img.width)
+               width = img.width;
+       else got_real = false;
+       disp.style.height = (height+ 30) + "px";
+       disp.style.width = (width + 0) + "px";
+       img.style.visibility = "visible";
+       return got_real;
+}
+
+function build_amazon_link(a) {
+       if(playing.asin&&playing.asin.length>0) {
+               a.href = "http://www.amazon.com/gp/product/" + playing.asin + "?ie=UTF8&tag=httppitchfork-20" +
+                        "&linkCode=as2&camp=1789&creative=9325&creativeASIN=" + playing.asin;
+               a.target = "_new";
+               return true;
+       }
+       else {
+               return false;
+       }
+}
+
+function recommend_init() {
+       var tmp = create_node("p");
+       tmp.className = "nomargin";
+       tmp.appendChild(create_txt(LANG.RECOMMEND_RECOMMENDATIONS));
+       var close = create_node("span", null, " ["+LANG.CLOSE+"]");
+       add_listener(close, "click", sidebar_close);
+       close.className = "fakelink";
+       tmp.appendChild(close);
+       sidebar.add_view("recommend", tmp, 20);
+}
+
+function recommend_open() {
+       sidebar_open("recommend");
+       sidebar.set_content(create_txt(LANG.WAIT_LOADING));
+       recommend_fetch_data(); 
+}
+
+function recommend_set_content(c) {
+       if(sidebar.open_view == "recommend")
+               sidebar.set_content(c);
+}
+
+function recommend_fetch_data() {
+       if(!playing.pl_size) {
+               recommend_set_content(LANG.RECOMMEND_EMPTY_PLAYLIST);
+               return;
+       }
+       var http = new XMLHttpRequest()
+       http.open("GET", "metadata.php?plrecommend", true);
+       http.onreadystatechange = function() {
+               if(http.readyState==4) {
+                       
+                       var result = http.responseXML.getElementsByTagName("result")[0];
+                       if(!result || result.textContent=="failed" || !result.hasChildNodes()) {
+                               recommend_set_content(create_txt(LANG.E_COMM_PROBLEM));
+                               return;
+                       }
+                       var exists = create_node("ul");
+                       var others = create_node("ul");
+                       exists.className = "recommended";
+                       others.style.paddingLeft = "10px";
+
+                       add_listener(exists, "click", recommend_toggle_open);
+                       add_listener(exists, "mousedown", stop_event);
+                       
+                       result = result.childNodes;
+                       for(var i=0; i < result.length; i++) {
+                               var a = result[i];
+                               var artist = get_tag(a, "name");
+                               var albums = a.getElementsByTagName( "album")[0];
+
+                               if(albums&&albums.hasChildNodes()) {
+                                       var list = create_node("li", null, artist);
+                                       var slist = create_node("ul");
+                                       add_listener(slist, "click", recommend_add_to_playlist);
+                                       var node = albums.firstChild;
+                                       while(node) {
+                                               var tmp = create_node("li", null, node.textContent);
+                                               tmp.title = LANG.RECOMMEND_ADDTOPLAYLIST;
+                                               tmp.setAttribute("artist", artist);
+                                               slist.appendChild(tmp);
+                                               node = node.nextSibling;
+                                       }
+                                       list.appendChild(slist);
+                                       exists.appendChild(list);
+                               }
+                               else {
+                                       var li = create_node("li", null, artist);
+                                       others.appendChild(li);
+                               }
+                       }
+                       var tmp = create_fragment();
+                       // todo: size
+                       tmp.appendChild(create_txt(LANG.RECOMMEND_SIMILAR));
+                       tmp.appendChild(exists);
+                       // todo: size
+                       tmp.appendChild(create_txt(LANG.RECOMMEND_ARTISTS));
+                       tmp.appendChild(others);
+                       recommend_set_content(tmp);
+               }
+       }
+       http.send(null);
+}
+
+function recommend_toggle_open(e) {
+       if(e.target.parentNode.className != "recommended")
+               return;
+       if(e.target.hasAttribute("open"))
+               e.target.removeAttribute("open");
+       else e.target.setAttribute("open", "k");
+}
+
+function recommend_add_to_playlist(e) {
+       if(!e.target.hasAttribute("artist")) 
+               return;
+       send_command("searchadd&artist=" + encodeURIComponent(e.target.getAttribute("artist")) + 
+               "&album=" + encodeURIComponent(e.target.textContent), browser_add_cb, LANG.WAIT_ADDING);
+}
diff --git a/std/playlist.js b/std/playlist.js
new file mode 100644 (file)
index 0000000..5bf3e9c
--- /dev/null
@@ -0,0 +1,907 @@
+/* 
+    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.
+*/
+
+/* this file contains anything pertaining to playlist management */
+
+var playlist_element = null;
+
+
+function playlist_init() {
+       show_status_bar(LANG.WAIT_LOADING, true);
+       var http = new XMLHttpRequest(); 
+       http.open("GET", "command.php?ping");
+       remove_children(playlist_element);
+
+       var c = create_node("tr");
+       function ctws(size) {
+               var tmp = create_node("td");
+               tmp.style.width = size + "px";
+               c.appendChild(tmp);
+       }
+       ctws(23); // pos
+       for(var i=1; i< pl_entries.length-1; i++) {
+               if(pl_entries[i]=="Track") {
+                       ctws(50);
+               }
+               else {
+                       ctws(200);
+               }
+       }
+       ctws(30); // time
+       c.className = "playlist";
+       pl_entry_clone = c;
+
+       http.onreadystatechange = function() {
+         if(http.readyState==4) {
+           var resp = null;
+           if(!http.responseText||!(resp = evaluate_json(http.responseText))) {
+               show_status_bar(LANG.E_INVALID_RESPONSE);
+               throw("Init failed");
+           }
+           else if(http.status==200) {
+             if(resp['connection']&&resp['connection']=="failed") {
+               show_status_bar(LANG.E_CONNECT);
+               throw(LANG.E_CONNECT);
+             }
+
+             /* for pagination to work properly we need to know some values in forehand */
+             if(resp.status) {
+               var s = resp.status;
+               if(typeof(s.song)!='undefined') 
+                       playing.pos = parseInt(s.song);
+             }
+
+             if(pagination_is_following())
+               playlist_scroll_to_playing();
+
+             hide_status_bar(1);
+             dirlist_init();
+             setTimeout(need_update,10);
+           }
+           else {
+               show_status_bar(LANG.E_INIT_PL);
+               throw("init failed");
+           }
+         }
+       }
+       http.send(null);
+}
+
+function create_lazy_pl_row(info, id) {
+       var tr = null;
+       var pos = parseInt(info["cpos"]);
+       tr = pl_entry_clone.cloneNode(true);
+       tr.firstChild.appendChild(create_txt(parseInt(pos)+1));
+       tr.setAttribute("plpos", pos);
+       tr.id = id;
+       return tr;
+}
+
+function get_plid(node) {
+       return node.id.substring(7);
+}
+
+function get_plpos(node) {
+       return node.getAttribute("plpos");
+}
+
+function set_pl_position(node, new_pos) {
+       node.childNodes[0].childNodes[0].nodeValue = (new_pos+1);
+       node.setAttribute("plpos", new_pos);
+}
+
+function moved_plitem(stat) {
+       if(stat!="ok") {
+               show_status_bar(LANG.E_PL_MOVE);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+}
+
+function playlist_get_move_txt() {
+       /* todo */
+       return "Moving selection..";
+}
+
+/* a note on this... if to is null then it was put first in the list */
+/* if move is null then it's a multi-move (which it always will be for now) */
+function playlist_move(move, to) {
+
+       if(move==null) {
+               var range = get_pl_selection_range(false);
+               to = get_plpos(to);
+               send_command("rangemove=" + encodeURIComponent(to) + "&elems=" + encodeURIComponent(range));
+               return;
+       }
+       
+       if(to==move) {
+         debug("trying to move to same, you should never see this");
+         return;
+       }
+
+       var from = get_plid(move);
+       var topos = get_plpos(to);
+       if(from<0 || topos == null || topos == "" ) {
+               debug("Missing id on to or from!");
+       }
+       send_command("playlist=move&from=" + from + "&to=" + topos, moved_plitem);
+}
+
+/* cut's of elements at the end of playlist */
+function playlist_resize(size) {
+       if(pagination.max>0&&pagination.pages>1) {
+               if(pagination.page==pagination.pages-1 && size%pagination.max!=0 ) {
+                       remove_children_after(playlist_element, size%pagination.max);
+               }
+       }
+       else {
+               remove_children_after(playlist_element, size);
+       }
+}
+
+function select_playing_song() {
+       var new_p = document.getElementById("plitem_" + playing.id);
+       if(playing.show_node) {
+               if(playing.show_node==new_p) // same node
+                       return;
+               apply_border_style_to_children(playing.show_node, "none");
+               playing.show_node.removeAttribute("playing");
+               playing.show_node = null;
+       }
+       if(new_p) {
+               apply_border_style_to_children(new_p, PLAYLIST_PLAYING_STYLE);
+               new_p.setAttribute("playing", "i");
+               playing.show_node = new_p;
+       }
+       
+}
+
+/* not really lazy, but it needs a name */
+function get_lazy_info(need_info) {
+       var ids = "";
+       var start_id=-1, last_id=-1, id;
+       for(id in need_info) {
+               if(start_id<0) {
+                       start_id = last_id = id;
+               }
+               else if(last_id==id-1) {
+                       last_id = id;
+               }
+               else {
+                       ids = add_range(ids, start_id, (start_id==last_id?false:last_id));
+                       start_id = last_id = id;
+               }
+       }
+       ids = add_range(ids, start_id, (start_id==last_id?false:last_id));
+
+       if(ids.length>0) {
+               need_info_arr = need_info;
+               /* get missing info and don't get plchanges */
+               send_command("playlist=info", get_lazy_info_cb, LANG.WAIT_UPDATING_PL, true, "ids=" + encodeURIComponent(ids), true);
+       }
+}
+
+function get_lazy_info_cb(res) {
+
+       var node,id,info;
+       var ple = pl_entries;
+
+       var need_info = need_info_arr;
+       need_info_arr = null;
+       if(need_info==null) {
+               //debug("get lazy info cb called, but no need_info array available");
+               /* FIXME: we have a potential race condition here 
+               (may get several results, but only one of them is the one we need and or can use) */
+               return;
+       }
+               
+       if(!res) {
+               show_status_bar(LANG.E_INVALID_RESULT);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+               return;
+       }
+       show_status_bar(LANG.WAIT_UPDATING_PL);
+       
+       /* todo: hide when more than 500 changes */
+       var length = res.length;
+       var plength = ple.length;
+       for(var i=0; i<length; i++) {
+               info = res[i];
+               var res_id = info.Id;
+               id = false;
+               node = need_info[res_id];
+               if(node) {
+                       info = res[i];
+                       for(var j=1; j<plength;j++) {
+                               var val = info[ple[j]];
+
+                               if(val) {
+                                       if(ple[j]=="Time") 
+                                               val = convert_minsec(val);
+                               }
+                               else { 
+                                       if(ple[j]=="Title") {
+                                               val = info["file"];
+                                               val = val.substring(val.lastIndexOf(DIR_SEPARATOR)+1);
+                                       }
+                                       else {
+                                               val = "";
+                                       }
+                               }
+                               if(val.length>29) {
+                                       node.childNodes[j].title = val;
+                                       val = val.substring(0,27) + "..";
+                               }
+                               else if(node.childNodes[j].title) {
+                                       node.childNodes[j].removeAttribute("title");
+                               }
+
+                               if(node.childNodes[j].hasChildNodes())
+                                       node.childNodes[j].childNodes[0].nodeValue = val;
+                               else node.childNodes[j].appendChild(create_txt(val));
+                       }
+               }
+       }
+       hide_status_bar();
+
+       if(browser_is_konqueror()) {
+               playlist_element.className = "pltemp";
+               setTimeout(cclass_for_konqueror, 1);
+       }
+}
+
+/* see bug #46 */
+function cclass_for_konqueror() {
+       playlist_element.className  = "";
+}
+
+function pl_selection_changed(sel) {
+       if(sel!=last_pl_selected) {
+               last_pl_selected = sel;
+               document.getElementById('crop_items_button').title = sel?LANG.CROP_SELECTION:LANG.CLEAR_PLAYLIST;
+       }
+}
+
+function plchanges_handler3(list, size) {
+       if(!list||!list.length||list.length<=0)
+               return;
+       var cont = playlist_element;
+       var df = create_fragment();  // temporary storage until we end
+       var buf = create_fragment(); // for appending
+       var i=0;
+       var id;
+       var plid;
+       var pointer = null; // working point in playlist
+       var cursor = null; // temporary point in playlist to make get from doc more effective
+       var need_info = new Array();
+       var count = 0;
+
+       /* returns the id of the next item in the list */
+       function _gn_id() {
+               if(i+1<list.length) 
+                       return "plitem_" + list[i+1]["Id"];
+               else return null;
+       }
+       /* checks if it is in cache */
+       function _get_from_df(id) {
+               var cf = df.childNodes;
+               for (var j=0; j<cf.length; j++) {
+                       if(cf[j].id&&cf[j].id==id) {
+                               return df.removeChild(cf[j]);
+                       }
+               }
+               return null;
+       }
+
+       function _get_from_doc(id) {
+               /* document.getElementById seems slow on large lists,
+                  and there are a few assumptions we can make.. */
+               if(!cursor)
+                       cursor = pointer.nextSibling;
+               var start_point = cursor;
+               var stop_at = false;
+               while(cursor) {
+                       if(cursor.id == id) {
+                               var ret = cursor;
+                               cursor = cursor.nextSibling;
+                               return cont.removeChild(ret);
+                       }
+                       cursor = cursor.nextSibling;
+                       if(!cursor&&start_point) {
+                               if(start_point!=pointer.nextSibling) {
+                                       stop_at = start_point;
+                                       start_point = false;
+                                       cursor = pointer.nextSibling;
+                               }
+                       }
+                       if(stop_at == cursor)
+                               break;
+               }
+               return null;
+       }
+       
+       /* create a row */
+       function _create_row(id) {
+               /* wow, what a mess.... */
+               n = create_lazy_pl_row(list[i], "plitem_" + id);
+               need_info[id] = n;
+               return n;
+       }
+
+       function _update_current_row(id) {
+               pointer.id = "plitem_" + id;
+               need_info[id] = pointer;
+       }
+
+       function _get_from_df_or_create(id) {
+               var n = _get_from_df("plitem_" + id);
+               if(!n) n = _create_row(id);
+               return n;
+       }
+
+       var changes_length = list.length;
+       var changes_start = parseInt(list[0]["cpos"]); 
+       var changes_end = parseInt(list[list.length-1]['cpos']);
+       var pagination_start = pagination.max * pagination.page;
+       var pagination_end = pagination.max + pagination_start;
+       var start_pos = 0;
+
+       var pagination_switched_page = pagination.need_update;
+
+       if(pagination.max==0) {
+               start_pos = changes_start;
+       }
+       else if(changes_start<=pagination_start&&changes_end>=pagination_start) {
+               i = pagination_start - changes_start;
+       }
+       else if(changes_start<pagination_end) {
+               i = 0; 
+               start_pos = changes_start - pagination_start;
+       }
+       else { // outside range
+               return; // let's hope we can just return...
+       }
+
+       if(start_pos<cont.childNodes.length&&start_pos>=0) {
+               pointer = cont.childNodes[start_pos];
+       }
+       else if(pointer==null) {
+               pointer = _create_row(list[0]["Id"]);
+               cont.appendChild(pointer);
+       }
+
+       if(start_pos==0) { // make sure there are no-one before us...
+               if(pointer.previousSibling) {
+                       // todo obviously (if needed)
+                       debug("let me know if you ever see this message (NTS, plchanges_handler)");
+               }
+       }
+
+       var append = false;
+       var length = list.length;
+       var n, nid; 
+       var max = pagination.max || Number.MAX_VALUE;
+
+       var _insert_before = insert_before;
+       var _replace_node = replace_node;
+
+       for(; i < length && start_pos++ < max; i++) {
+               id = list[i]["Id"];
+               if(append) {
+                       n = _get_from_df_or_create(id)
+                       buf.appendChild(n);
+                       continue;
+               }
+               plid = "plitem_" + id;
+
+               // if it's a page switch everything will have to be updated
+               if(pagination_switched_page) {
+                       _update_current_row(id); 
+               }
+               else if(pointer.id!=plid) {
+                       nid = _gn_id();
+                       n = _get_from_df(plid);
+                       /* if n is found in df it has been removed, but wants back in */
+                       if(n||nid==null||pointer.id==nid) {
+                               if(!n)
+                                       n = _get_from_doc(plid);
+                               if(!n)
+                                       n = _create_row(id);
+                               _insert_before(n, pointer);
+                               //debug("insert");
+                       }
+                       else {
+                               if(!n)
+                                       n = _get_from_doc(plid);
+                               if(!n)
+                                       n = _create_row(id);
+                               _replace_node(n,pointer);
+                               df.appendChild(pointer);
+                               //debug("replace");
+                       }
+                       pointer = n;
+               }
+               if(pointer.nextSibling)
+                       pointer = pointer.nextSibling;
+               else 
+                       append = true;
+       }
+
+       if(buf.hasChildNodes())
+               cont.appendChild(buf);
+       if(need_info.length>0) 
+               get_lazy_info(need_info);
+
+       playlist_resize(size);
+       update_node_positions2(cont);
+       select_playing_song();
+
+       // must be called last
+       if(pagination_switched_page)
+               pagination_post_pageswitch();
+}
+
+function update_node_positions2(container) {
+       var node = container.firstChild;
+       var found_diff = false;
+       var i = pagination.max * pagination.page;
+       while(node) {
+               if(found_diff) {
+                       set_pl_position(node, i++);
+                       node = node.nextSibling;
+               }
+               else {
+                       if(get_plpos(node)==i) {
+                               node = node.nextSibling;
+                               i++;
+                       }
+                       else {
+                               found_diff = true;
+                       }
+               }
+       }
+       return i;
+}
+
+function pagination_init() {
+
+       pagination.following = null; // set to true/false in pagination_is_following
+
+       /* these values shouldn't be hard-coded, but they are */
+       pagination.left =   50;
+       pagination.width = 680;
+
+       pagination.scroll_id = false;
+       pagination.scroll_left = false;
+
+       pagination.follow_button = document.getElementById("pagination_follow_current");
+       pagination.scroll_to_pos_after_switch = false;
+       pagination_update_follow();
+}
+
+/* should only be called when the size of the list has been changed and pages no longer fit*/
+function pagination_update_list(size) {
+       if(pagination.max<=0) 
+               return;
+       var l = pagination.list;
+       var npages = Math.ceil(size/pagination.max); // number of pages
+       var cpages = pagination.pages; // current number of pages
+       var cpage = pagination.page;
+
+       while(npages>cpages) {
+               var n = add_li(l, cpages+1);
+               n.setAttribute("page", cpages++);
+       }
+       while(npages<cpages) {
+               remove_node(l.lastChild);
+               cpages--;
+       }
+
+       pagination.container.style.display = cpages>1?"block":"";
+
+       pagination.pages = cpages;
+       /* if we have switched page it needs update */
+       if(cpage>=cpages) {
+               cpage = cpages - 1;
+       }
+       pagination_update_current_page(cpage);
+}
+
+/* c: new page number */
+function pagination_update_current_page(c) {
+       if(pagination.max<=0) 
+               return;
+       var p = pagination.list.firstChild;
+       
+       if(pagination.cpage&&pagination.cpage.hasAttribute("cpage")) 
+               pagination.cpage.removeAttribute("cpage");
+
+       while(p) {
+               if(p.getAttribute("page")==c) {
+                       scrollIntoView(p);
+                       p.setAttribute("cpage", "1");
+                       break;
+               }
+               p = p.nextSibling;
+       }
+       pagination.cpage = p;
+       pagination.page = c;
+}
+
+function pagination_fetch_current_page() {
+       playing.pl_version = -1; 
+       pagination.need_update = true;
+       reschedule_update_now();
+}
+
+function pagination_change_page(e, page) {
+       if(e!=null&&e.target.hasAttribute("page")) {
+               pagination.page = parseInt(e.target.getAttribute("page"));
+       }
+       else if(typeof(page)!='undefined') {
+               pagination.page = page;
+       }
+       else {
+               return;
+       }
+       
+       pagination_update_current_page(pagination.page);
+       pagination_fetch_current_page();
+       unselect_all_nodes(playlist_element);
+}
+
+function pagination_scroll_view(e) {
+       var x = e.pageX - pagination.left;
+       var left = false;
+       var abort = true;
+
+       if(x>pagination.width-20) {
+               abort = false;
+       }
+       else if(x<20&&pagination.list.scrollLeft>0) {
+               abort = false;
+               left = true;
+       }
+
+       if(!pagination.scroll_id&&!abort) {
+               pagination_scroll_real(left);
+       }
+       else if(pagination.scroll_id&&abort) {
+               pagination_scroll_stop();
+       }
+
+       stop_event(e);
+}
+
+function pagination_scroll_stop(e) {
+
+       if(e && e.relatedTarget && (e.relatedTarget.id=="pagination_list"||e.relatedTarget.parentNode.id=="pagination_list"||typeof(e.relatedTarget.page)!='undefined'))
+               return;
+       
+       if(pagination.scroll_id) {
+               clearTimeout(pagination.scroll_id);
+               pagination.scroll_id = false;
+       }
+}
+
+function pagination_is_following() {
+       if(pagination.following == null) {
+               var f = setting_get("follow_playing");
+               if(f&&parseInt(f))
+                       pagination.following = true;
+               else pagination.following = false;
+       }
+       return pagination.following;
+}
+
+function pagination_set_following(follow) {
+       setting_set("follow_playing", (follow?"1":"0"));
+       pagination.following = follow;
+       pagination_update_follow();
+}
+
+function pagination_toggle_following() {
+       pagination_set_following(!pagination_is_following());
+}
+
+function pagination_update_follow() {
+       if(!pagination.follow_button)
+               return;
+       if(pagination_is_following()) {
+               pagination.follow_button.src = IMAGE.PAGINATION_FOLLOW;
+               pagination.follow_button.title = LANG.PAGINATION_FOLLOW;
+       }
+       else {
+               pagination.follow_button.src = IMAGE.PAGINATION_NOFOLLOW;
+               pagination.follow_button.title =LANG.PAGINATION_NOFOLLOW;
+       }
+}
+
+/** left or right */
+function pagination_scroll_real(left) {
+       var l = pagination.list;
+       var adjust = 0;
+
+       if(left) {
+               if(l.scrollLeft>0)
+                       adjust = -10;
+               else return;
+       }
+       else {
+               adjust = 10;
+       }
+       try {
+               l.scrollLeft += adjust;
+               if(pagination.scroll_id)
+                       clearTimeout(pagination.scroll_id);
+               pagination.scroll_id = setTimeout(pagination_scroll_real, 50, left);
+       }catch(e) {debug(e.message);}
+}
+
+function pagination_post_pageswitch() {
+       pagination.need_update = false;
+       if(pagination.scroll_to_pos_after_switch!=false) {
+               var n = playlist_scroll_to_pos_real(pagination.scroll_to_pos_after_switch);
+               pagination.scroll_to_pos_after_switch = false; 
+               if(n) {
+                       unselect_all_nodes(playlist_element);
+                       select_node(n);
+               }
+       }
+       else if(pagination_is_following()&&playing.show_node) {
+               playlist_scroll_to_pos_real(playing.pos);
+       }
+}
+
+
+/* Returns false if have to switch page, true otherwise */
+function playlist_scroll_to_playing() {
+       return playlist_scroll_to_pos(playing.pos);
+}
+
+/* set select to true if all other nodes should be unselected and the right node selected */
+function playlist_scroll_to_pos(pos, select) {
+       if(pagination.max>0) {
+               if(pos<0) pos = 0;
+               var page = Math.floor(pos/pagination.max);
+               if(page!=pagination.page) {
+                       pagination_change_page(null, page);
+                       if(select) 
+                               pagination.scroll_to_pos_after_switch = pos;
+                       return false; // selecting handled in pagination_post_pageswitch
+               }
+       }
+       var n = playlist_scroll_to_pos_real(pos);
+       if(select) {
+               unselect_all_nodes(playlist_element);
+               select_node(n);
+       }
+       return true;
+}
+
+function playlist_scroll_to_pos_real(pos) {
+               if(pagination.max>0) {
+                       pos = pos%pagination.max
+               }
+
+               if(pos<0) {
+               }
+               else if(pos<playlist_element.childNodes.length) {
+                       var n = playlist_element.childNodes[pos];
+                       window.scrollTo(0,n.offsetTop -20);
+                       return n;
+               }
+               else if(playlist_element.childNodes.length) {//if something in playlist, nag about it
+                       debug("scroll to node request outside range");
+               }
+               return null;
+}
+
+function get_pl_selection_range(invert) {
+       var c = playlist_element.childNodes;
+       var sel = "";
+       var pagination_add = invert&&pagination.max>0;
+
+       /* if we have pagination and not on page one, we need to add everything up until this page first */
+       if(pagination_add&&pagination.page>0) {
+               sel = add_range(sel, 0, pagination.max * pagination.page -1);
+       }
+
+       var tmp_start = null;
+       var tmp_stop = null;
+       var length = c.length;
+       for(var i=0; i<length; i++) {
+               var selected = is_node_selected(c[i]);
+               if(invert)
+                       selected = !selected;
+               if(selected) {
+                       if(!tmp_start)
+                               tmp_start = c[i];
+                       else tmp_stop = c[i];
+               }
+               else if(tmp_start) {
+                       tmp_start = get_plpos(tmp_start);
+                       if(tmp_stop)
+                               tmp_stop = get_plpos(tmp_stop);
+                       sel = add_range(sel, tmp_start, tmp_stop);
+                       tmp_start = null;
+                       tmp_stop = null;
+               }
+       }
+       if(tmp_start) {
+               tmp_start = get_plpos(tmp_start);
+               // todo: what if not proper last node
+               if(tmp_stop)
+                       tmp_stop = get_plpos(tmp_stop);
+               sel = add_range(sel, tmp_start, tmp_stop);
+       }
+
+       // add after this page
+       if(pagination_add&&pagination.page+1<pagination.pages) {
+               sel = add_range(sel, (pagination.page+1)*pagination.max, playing.pl_size-1); 
+       }
+       return sel;
+}
+
+function playlist_dblclick(elem, e) {
+       var id = get_plid(elem);
+       if(id>=0) {
+               var cmd = "act=play&id=" + get_plid(elem);
+               send_command(cmd);
+       }
+}
+
+function playlist_add_button(e) {
+       if(!playlist_add_popup) {
+               playlist_add_popup = playlist_add_create_content(
+                                       document.getElementById("playlist_add"));
+       }
+       stop_event(e);
+       playlist_add_popup.show();
+}
+
+function playlist_save_button(e) {
+       if(!playlist_save_popup) {
+               playlist_save_popup = playlist_save_create_content(
+                                       document.getElementById("playlist_save"));
+       }
+       stop_event(e);
+       playlist_save_popup.show();
+}
+
+/* these functions should be merged somehow */
+function playlist_add_create_content(padd) {
+       var cats = new Array(LANG.BY_URL); //, "From file", "Text");
+       var c = create_fragment();
+       var ul = create_node("ul");
+       ul.className = "playlist_popup";
+       c.appendChild(ul);
+       var li;
+       for(var i=0; i < cats.length; i++) {
+               li = add_li(ul, cats[i]);
+               li.className = "playlist_popup";
+       }
+       li = add_li(ul, LANG.CLOSE);
+       li.className = "playlist_popup";
+       add_listener(li, "click", playlist_add_close);
+       var d = create_node("div");
+       c.appendChild(d);
+
+       var tmp = create_node("input", "playlist_add_url");
+       add_listener(tmp, "keydown", playlist_add_by_url);
+       d.appendChild(tmp);
+
+       tmp = create_node("span");
+       d.appendChild(tmp);
+       tmp.className = "playlist_popup";
+       tmp.appendChild(create_txt(" Add"));
+       add_listener(tmp, "click", playlist_add_by_url);
+
+       var pop = new Popup(padd, c);
+       pop.popup.style.marginLeft = "-140px";
+       /* don't let the click get anywhere else either */
+       add_listener(pop.popup, "click", stop_event);
+       return pop;
+}
+
+function playlist_save_create_content(psave) {
+       var c = create_fragment();
+       var tmp = create_node("p");
+       tmp.className = "nomargin";
+       tmp.appendChild(create_txt(LANG.PL_SAVE_AS));
+       var close = create_node("span");
+       add_listener(close, "click", playlist_save_close);
+       close.appendChild(create_txt(LANG.CLOSE));
+       close.className = "playlist_popup";
+       tmp.appendChild(close);
+       tmp.appendChild(create_node("br"));
+       c.appendChild(tmp);
+       tmp = create_node("input", "playlist_save_box");
+       add_listener(tmp, "keydown", playlist_save_listener);
+       c.appendChild(tmp);
+       tmp = create_node("span");
+       tmp.appendChild(create_txt(" " + LANG.SAVE));
+       tmp.className = "playlist_popup";
+       add_listener(tmp, "click", playlist_save_listener);
+       c.appendChild(tmp);
+
+       var pop = new Popup(psave, c);
+       pop.popup.style.marginLeft = "-140px";
+       /* don't let the click get anywhere else either */
+       add_listener(pop.popup, "click", stop_event);
+       return pop;
+}
+
+function playlist_add_by_url(e) {
+       stop_propagation(e);
+       if((e.type=="keydown"&&e.keyCode==RETURN_KEY_CODE)||e.type=="click") {
+               stop_event(e);
+               var p = document.getElementById("playlist_add_url");
+               var url = p.value;
+               url = url.trim();
+               if(url.length>6) {
+                       send_command("playlist_add_url=" + encodeURIComponent(url), 
+                                       playlist_add_by_url_cb, LANG.WAIT_ADDING_PL);
+                       p.value = "";
+                       playlist_add_close();
+               }
+       }
+}
+
+function playlist_save_listener(e) {
+       stop_propagation(e);
+       if((e.type=="keydown"&&e.keyCode==RETURN_KEY_CODE)||e.type=="click") {
+               stop_event(e);
+               var p = document.getElementById("playlist_save_box");
+               var name = p.value;
+               name = name.trim();
+               if(name.length>0) {
+                       send_command("playlist_save=" + encodeURIComponent(name), playlist_save_cb, LANG.PL_SAVING);
+                       p.value = "";
+                       playlist_save_popup.hide();
+               }
+       }
+}
+
+function playlist_add_by_url_cb(result) {
+       if(result=="failed") {
+               show_status_bar(LANG.E_FAILED_ADD_PL);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+       else {
+       }
+}
+
+function playlist_save_cb(result) {
+       if(result=="failed") {
+               show_status_bar(LANG.E_FAILED_SAVE_PL);
+               hide_status_bar(STATUS_DEFAULT_TIMEOUT);
+       }
+}
+
+function playlist_add_close(e) {
+       if(e)
+               stop_event(e);
+       if(playlist_add_popup)
+               playlist_add_popup.hide();
+}
+function playlist_save_close(e) {
+       if(e)
+               stop_event(e);
+       if(playlist_save_popup)
+               playlist_save_popup.hide();
+}
diff --git a/std/plsearch.js b/std/plsearch.js
new file mode 100644 (file)
index 0000000..74094ca
--- /dev/null
@@ -0,0 +1,212 @@
+/* 
+    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.
+*/
+
+
+var sidebar = null;
+
+function Sidebar() {
+       this.is_open = false;
+       this.view = new HashMap();
+       this.header_height = new HashMap();
+       this.open_view = "metadata";
+       this.header_height.put("metadata", 20);
+       /* TODO: find another way to determine the height of this */
+       this.display_base_top = 107;
+       this.header = document.getElementById("sidebar_header");
+       this.display = document.getElementById("sidebar_display");
+       this.display_txt = document.getElementById("sidebar_display_txt");
+       this.last_metadata_request = null;
+       this.PLSEARCH_OPTS = new Array("Any", "Artist", "Title", "Album", "Genre", "Filename", "Composer", "Performer", "Date");
+       this.plsearch_choice = null; // select box
+       this.plsearch_input = null; // search input box
+}
+
+Sidebar.prototype.say_loading = function(txt) {
+       if(typeof(txt)=='undefined')
+               txt = LANG.WAIT_LOADING;
+       this.set_content(create_txt(txt));
+}
+
+Sidebar.prototype.adjust_height = function(name) {
+       var h = this.header_height.get(name);
+       this.display.style.top = (this.display_base_top + h) + "px";
+       this.header.style.height = h + "px";
+}
+
+Sidebar.prototype.set_content = function(fragment) {
+       remove_children(this.display_txt);
+       this.display_txt.appendChild(fragment);
+}
+
+/* Name of view, buffer/element that is this view, and height of view */
+Sidebar.prototype.add_view = function(name, buffer, height) {
+       this.view.put(name,buffer);
+       if(!height)
+               height = 20;
+       this.header_height.put(name, height);
+}
+
+/* will switch to the specified view if it isn't already open */
+Sidebar.prototype.switch_view = function(name) {
+       if(this.open_view==name) 
+               return;
+       var n = this.view.remove(name); // make sure we can get it first
+       if(!n) {
+               debug("can't get new sidebar view: " + name);
+               return;
+       }
+       var buf = remove_children(this.header);
+       this.view.put(this.open_view, buf);
+       this.header.appendChild(n);
+       this.open_view = name;
+}
+
+
+function sidebar_close() {
+       if(browser_is_opera()) {
+               sidebar.header.style.display = "none";
+               opera_quirk_set_display_none(sidebar.display);
+       }
+       else {
+               sidebar.header.style.display = "none";
+               sidebar.display.style.display = "none";
+       }
+       remove_children(sidebar.display_txt);
+}
+
+function sidebar_open(view) {
+       if(view&&view!=sidebar.open_view) { // we have to change view
+               sidebar.switch_view(view);
+               sidebar.adjust_height(view);
+       }
+       sidebar.header.style.display = "block";
+       sidebar.display.style.display = "block";
+}
+
+
+function sidebar_init() {
+       sidebar = new Sidebar();
+}
+
+function plsearch_init() {
+       /* you can either use a buffer like in create_fragment()
+        * or add it to a top-container like I've done here*/
+       var t = create_node("p");
+       t.className = "nomargin";
+       t.appendChild(create_txt("Playlistsearch:  "));
+       var close = create_node("span", null, " ["+LANG.CLOSE+"]");
+       add_listener(close, "click", sidebar_close);
+       close.className = "fakelink";
+       t.appendChild(close);
+       t.appendChild(create_node("br"));
+
+       var search = create_search_choices(sidebar.PLSEARCH_OPTS, plsearch_choice_change)
+
+       t.appendChild(search);
+
+       sidebar.plsearch_choice = search;
+       search = create_node("input");
+       search.type = "text";
+       sidebar.plsearch_input = search;
+       add_listener(search, "keyup", plsearch_term_change);
+       add_listener(search, "keydown", stop_propagation);
+       search.className = "browse_type";
+       t.appendChild(search);
+       
+       sidebar.add_view("plsearch", t, 45);
+}
+
+function plsearch_choice_change(e) {
+       stop_propagation(e);
+       sidebar.plsearch_input.focus();
+}
+
+function plsearch_open() {
+       sidebar_open("plsearch");
+       sidebar.set_content(create_txt(""));
+       sidebar.plsearch_input.focus();
+}
+
+function plsearch_set_content(content) {
+       if(sidebar.open_view=="plsearch") 
+               sidebar.set_content(content);
+}
+
+function plsearch_term_change(e) {
+       stop_propagation(e); // we'll keep it
+       if(e.keyCode == RETURN_KEY_CODE) { // send search
+               var s = sidebar.plsearch_input.value.trim();
+               if(s.length>0) {
+                       send_command("plsearch=" + sidebar.plsearch_choice.selectedIndex + 
+                               "&s=" + s, plsearch_search_cb, LANG.WAIT_SEARCHING);
+               }
+               else { // clear results
+                       // fixme, possible leak
+                       remove_listener(sidebar.display.firstChild , "click", plsearch_click);
+                       remove_listener(sidebar.display.firstChild , "mousedown", stop_event);
+                       plsearch_set_content(create_txt(""));
+               }
+       }
+}
+
+function plsearch_search_cb(resp) {
+       if(typeof(resp)!='undefined'&&resp!="failed") {
+               var dst= create_node("p");
+               dst.style.padding = "0px";
+               for(var i=0; i<resp.length; i++) {
+                       var file = resp[i]["file"];
+                       var artist = resp[i]["Artist"];
+                       var title = resp[i]["Title"];
+                       var pos = resp[i]["Pos"];
+                       var name = "";
+                       if(title==null||!title.length) {
+                               name = file.substring(file.lastIndexOf(DIR_SEPARATOR)+1);
+                       }
+                       else {
+                               name = artist + " - " + title;
+                       }
+
+                       var e = create_node("span", null, name);
+                       e.className = "plse";
+                       e.setAttribute("diritem", file);
+                       e.setAttribute("dirtype", "file");
+                       e.setAttribute("plpos", pos);
+                       dst.appendChild(e);
+               }
+               plsearch_set_content(dst);
+               add_listener(dst, "click", plsearch_click);
+               add_listener(dst, "mousedown", stop_event);
+       }
+       else {
+               plsearch_set_content(create_txt(LANG.E_INVALID_RESULT));
+       }
+}
+
+function plsearch_click(e) {
+       stop_event(e);
+       var target = e.target;
+       if(target&&target.hasAttribute("plpos")) {
+               if(e.detail==1) {
+                       playlist_scroll_to_pos(parseInt(target.getAttribute("plpos")), true);   
+               }
+               else if(e.detail==2) {
+                       send_play_pos(target.getAttribute("plpos"));
+               }
+       }
+}
+
diff --git a/std/quickadd.js b/std/quickadd.js
new file mode 100644 (file)
index 0000000..b57de56
--- /dev/null
@@ -0,0 +1,186 @@
+/* 
+    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.
+*/
+
+var quickadd_last_term = "";
+
+function quickadd_focus(e) {
+       var qa = document.getElementById('quickadd');
+       var qa_disp = document.getElementById('qa_suggestions');
+       if(e&&e.type&&e.type!="focus");
+               qa.focus();
+       if(qa.value==qa.defaultValue) {
+               qa.value = "";
+       }
+       qa_disp.style.display = "block";
+}
+function quickadd_blur(e) {
+       var qa = document.getElementById('quickadd');
+       var qa_disp = document.getElementById('qa_suggestions');
+
+       if(qa.value.trim().length==0) {
+               qa.value=qa.defaultValue;
+               quickadd_hide();
+       }
+}
+function quickadd_keydown_handler(e) { 
+       e.stopPropagation(); 
+       var qa = document.getElementById('quickadd');
+       var key = e.keyCode;
+
+       /* return key, send request to add if something to add */
+       if(key==RETURN_KEY_CODE) {
+               stop_event(e);
+               var add = qa.value;
+               add = add.trim();
+               if(add.length>0) {
+                       var txt_node = document.getElementById('qa_suggestions_txt');
+                       var elems = txt_node.getElementsByTagName("span");
+                       var i = qa_get_selected_id(elems);
+                       if(i>=0) {
+                               add = elems[i].name;
+                       }
+                       quickadd_clean();
+                       send_command("add=" + encodeURIComponent(add), function(response) 
+                                       { if(response=="failed") show_status_bar(LANG.E_FAILED_ADD); }, 
+                                       LANG.WAIT_ADDING);
+               }
+               else {
+                       quickadd_clean();
+               }
+       }
+       else if(key==27) { // esc :(
+               stop_event(e);
+               qa.value = "";
+               qa.blur();
+               quickadd_hide();
+       }
+       else if(key>=37&&key<=40) { /* left up right down */
+               if(key==40) { // down
+                       quickadd_move_selection(1);
+                       stop_event(e);
+               }
+               else if(key==38) {  // up
+                       quickadd_move_selection(-1);
+                       stop_event(e);
+               }
+               else if(key==39) { // right
+                       var txt_node = document.getElementById('qa_suggestions_txt');
+                       var elems = txt_node.getElementsByTagName("span");
+                       var sel = qa_get_selected_id(elems);
+                       if(sel>=0) {
+                               //stop_event(e);
+                               qa.value = elems[sel].name + "/";
+                               quickadd_keyup_handler();
+                               qa.focus();
+                               setCaretToEnd(qa);
+                       }
+               }
+               else if(key==37) { // left
+               }
+       }
+}
+
+function quickadd_clean() {
+       var qa = document.getElementById('quickadd');
+       qa.value = "";
+       qa.blur();
+       quickadd_hide();
+}
+
+function qa_get_selected_id(elems) {
+       for(var i=0; i<elems.length; i++) {
+               if(elems[i].hasAttribute("qa_selected")) {
+                       return i;
+               }
+       }
+       return -1;
+}
+
+function quickadd_move_selection(num) {
+       var txt_node = document.getElementById('qa_suggestions_txt');
+       var elems = txt_node.getElementsByTagName("span");
+       var sel_node = qa_get_selected_id(elems);
+
+       if(sel_node>=0) {
+               elems[sel_node].removeAttribute("qa_selected");
+       }
+
+       num = num+sel_node;
+
+       if(num>=elems.length ||num==-1) {
+               return;
+       }
+       else if(num<0) // flip it around
+               num=elems.length-1;
+       elems[num].setAttribute("qa_selected", "omg");
+       /* safari workaround */
+       elems[num].className = elems[num].className;
+}
+
+function quickadd_hide() {
+       var txt_node = document.getElementById('qa_suggestions_txt');
+       var qa_disp = document.getElementById('qa_suggestions');
+       qa_disp.style.display = "none";
+       remove_children(txt_node);
+}
+
+function quickadd_keyup_handler(e) {
+       var qa = document.getElementById('quickadd');
+       var search_str = qa.value;
+       search_str = search_str.trim();
+
+       /* unwanted event */
+       if(e) {
+               e.stopPropagation(); 
+               if(e.altKey||e.metaKey||e.ctrlKey) {
+                       return;
+               }
+       }
+
+       if(search_str.length>0) {
+               if(search_str!=quickadd_last_term) {
+                       quickadd_last_term = search_str;
+                       send_command("quick_search=" + encodeURIComponent(search_str), quickadd_result_handler);
+               }
+       }
+       else {
+               var txt_node = document.getElementById('qa_suggestions_txt');
+               remove_children(txt_node);
+               quickadd_last_term = "";
+       }
+}
+
+function quickadd_result_handler(res) {
+       var txt_node = document.getElementById('qa_suggestions_txt');
+       if(!res||res=="failed") {
+               remove_children(txt_node);
+               txt_node.appendChild(create_txt(LANG.E_NOTHING_FOUND));
+       }
+       else {
+               remove_children(txt_node);
+               for(var ix in res) {
+                       var name = res[ix];
+                       var node = create_node("span");
+                       node.className = "qa_element";
+                       node.name = name;
+                       var idx = name.lastIndexOf(DIR_SEPARATOR);
+                       node.appendChild(create_txt((idx>0?"..":"") + name.substring(idx)));
+                       txt_node.appendChild(node);
+               }
+       }
+}
diff --git a/std/streaming.js b/std/streaming.js
new file mode 100644 (file)
index 0000000..358c2e6
--- /dev/null
@@ -0,0 +1,234 @@
+/* 
+    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.
+*/
+
+var streaming_info = null;
+
+function StreamingInfo() {
+       this.open = false;
+       this.display = null;
+       var c = setting_get("sap");
+       if(c==null) {
+               this.auto_play = true;
+               setting_set("sap", "true");
+       }
+       else {
+               this.auto_play = c=="true"?true:false;
+       }
+       this.auto_play_node = null;
+       this.applet = null;
+       this.notification_txt = null;
+       this.check_delay = 0.3 * 1000;
+       this.eventListeners = new Array();
+}
+
+function streaming_init() {
+       streaming_info = new StreamingInfo();
+}
+
+function streaming_destroy() {
+       while(streaming_info.eventListeners.length)
+               streaming_info.eventListeners.pop().unregister();
+       
+       remove_node(streaming_info.applet);
+       remove_node(streaming_info.auto_play_node);
+       remove_node(streaming_info.notification_txt);
+       remove_node(streaming_info.display);
+
+       streaming_info.applet = null;
+       streaming_info.auto_play_node = null;
+       streaming_info.notification_txt = null;
+       streaming_info.display = null;
+
+       streaming_info.open = false;
+}
+
+function streaming_open(e) {
+       var s =streaming_info.display; 
+       if(!s) {
+               s = create_node("div", "streaming_display");
+               var d2 = create_node("div");
+               d2.style.marginLeft = "5px";
+               if(!window.SHOUT_URL)
+                       return;
+               /* if someone can make this work in all browsers and not crash/hang
+                  anyone of them I'd like to know... */
+
+               function make_item(txt) {
+                       var item = create_node("li");
+                       item.className = "fakelink";
+                       item.style.borderLeft = item.style.borderRight = "none";
+                       add_txt(item, txt);
+                       return item;
+               }
+
+               var obj = "<applet type='application/x-java-applet'" +
+                         " width='70'" + 
+                         " height='32'" +
+                         " id='streamplayer'" + 
+                         " style='display: inline; visibility: hidden; position: absolute;'" +
+                         " archive='../jorbis/jorbis-pitchfork.jar'" +
+                         " classid='java:JOrbisPlayer.class'" +
+                         " code='JOrbisPlayer.class'" +
+                         "<param name='archive' value='../jorbis/jorbis-pitchfork.jar' />" +
+                         (streaming_info.auto_play?"<param name='jorbis.player.playonstartup' value='yes' />":"") +
+                         "<param name='jorbis.player.play.0' value='"+SHOUT_URL+"' />" +
+                         "<param name='jorbis.player.bgcolor' value='" + IMAGE.STREAM_BGCOLOR + "' />" + 
+                         "</applet>";
+               d2.innerHTML = obj;
+               s.appendChild(d2);
+               var txt = create_node("ul");
+               //txt.className = "fakelink";
+               txt.className = "nomargin";
+               //txt.style.margin = "0px 2px 0px 0px";
+               txt.style.padding = "0px 0px 0px 0px";
+               txt.style.fontSize = "smaller";
+
+               var sp = create_node("span");
+               sp.style.fontWeight = "bold";
+               sp.style.padding = "5px 0px 5px 0px";
+               add_txt(sp, LANG.STREAMING);
+               txt.appendChild(sp);
+
+               var item = make_item(LANG.CLOSE);
+               streaming_info.eventListeners.push(add_listener(item, "click", streaming_destroy));
+               txt.appendChild(item);
+
+               item = make_item(LANG.HIDE);
+               streaming_info.eventListeners.push(add_listener(item, "click", streaming_hide));
+               txt.appendChild(item);
+               
+               item = make_item( streaming_info.auto_play?LANG.AUTOPLAY:LANG.NO_AUTOPLAY );
+               txt.appendChild(item);
+               streaming_info.eventListeners.push(add_listener(item, "click", streaming_toggle_auto_play));
+               streaming_info.auto_play_node = item;
+
+               item = make_item("");
+               streaming_info.notification_txt = item;
+               txt.appendChild(streaming_info.notification_txt);
+               streaming_info.eventListeners.push(add_listener(streaming_info.notification_txt, "click", streaming_toggle_event));
+
+               // insert container first in area
+               insert_first(txt, s);
+
+               document.getElementById('player_control').appendChild(s);
+               streaming_info.display = s;
+               streaming_info.applet = document.applets['streamplayer'];
+
+               streaming_check_playing();
+               document.body.focus();
+       }
+       if(streaming_info.open) {
+               streaming_hide(e);
+       }
+       else {
+               s.style.visibility = "";
+               streaming_info.open = true;
+               streaming_try_autoplay();
+               if(e) {
+                       stop_event(e);
+               }
+       }
+}
+
+/* hides the whole streaming area */
+function streaming_hide(e) {
+       if(streaming_info.display) {
+               streaming_info.display.style.visibility = "hidden";
+       }
+       streaming_info.open = false;
+       if(e) {
+               stop_event(e);
+       }
+}
+
+/* toggles the autoplay feature */
+function streaming_toggle_auto_play(e) {
+       if(e) stop_event(e);
+       if(streaming_info.auto_play_node) {
+               var s = streaming_info.auto_play_node;
+               remove_children(s);
+               streaming_info.auto_play = !streaming_info.auto_play;
+               add_txt(s, streaming_info.auto_play?LANG.AUTOPLAY:LANG.NO_AUTOPLAY);
+               setting_set("sap", streaming_info.auto_play?"true":"false");
+       }
+}
+
+/* checks whether the applet is currently streaming or not, 
+ * returns false on error or non-existing applet */
+function streaming_is_playing() {
+       if(streaming_info.applet) {
+               try {
+                       return streaming_info.applet.isPlaying();
+               } catch(e) { } 
+       }
+       return false;
+}
+
+/* tries to start playback if the applet is available */
+function streaming_try_play() {
+       if(streaming_info.applet) {
+               try {
+                       streaming_info.applet.play_sound();
+               } catch(e) { }
+       }
+}
+
+/* tries to stop playback if the applet is available */
+function streaming_try_stop() {
+       if(streaming_info.applet) {
+               try {
+                       streaming_info.applet.stop_sound();
+               } catch(e) { }
+       }
+}
+
+/* tries to start playing if autoplay is enabled */
+function streaming_try_autoplay() {
+       if(streaming_info.auto_play&&streaming_info.display&&streaming_info.applet) {
+               streaming_try_play();
+       }
+}
+
+/* tries to stop the audio playback if autoplay is enabled */
+function streaming_try_autostop() {
+       if(streaming_info.auto_play&&streaming_info.display) {
+               streaming_try_stop();
+       }
+}
+
+function streaming_update_stat() {
+       remove_children(streaming_info.notification_txt);
+       streaming_info.notification_txt.appendChild(create_txt(streaming_info.stat?LANG.STOP:LANG.PLAY));
+}
+
+function streaming_check_playing() {
+       var stat = streaming_is_playing();
+       if(streaming_info.stat != stat) {
+               streaming_info.stat = stat;
+               streaming_update_stat();
+       }
+       setTimeout(streaming_check_playing, streaming_info.check_delay);
+}
+function streaming_toggle_event(e) {
+       if(e) stop_event(e);
+       if(!streaming_is_playing()) {
+               streaming_try_play();
+       } else {
+               streaming_try_stop();
+       }
+}
diff --git a/std/toolkit.js b/std/toolkit.js
new file mode 100644 (file)
index 0000000..df2149e
--- /dev/null
@@ -0,0 +1,1453 @@
+/* 
+    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.
+*/
+
+
+/**
+ * Why reinvent the wheel when you can make a hexadecagon instead -- Me
+ * 
+ * -- OR --
+ * 
+ * Reinventing the wheel to run myself over -- FOB
+ */
+
+var moveables = new Array(); 
+var move_idx = -1;
+
+var sliders = new Array();
+var slider_idx = -1;
+
+var overlay = new Array(); 
+var overlay_adjust = 100; // px
+var open_overlay_idx = -1;
+var overlay_time = 5;
+var overlay_hide_on_resize = true; // hide content on resize?
+
+var xpath_queries;
+
+function debug(str, force) {
+       
+       /* switch && false to true on release */
+       if(typeof(force)=="undefined" && true) {
+               return;
+       }
+
+       var d = document.getElementById('debugbox');
+       if(d==null) {
+         d = create_node("div");
+         d.id = "debugbox"; 
+         document.body.appendChild(d);
+       }
+
+       var n = create_node("p", null, str);
+       n.style.padding = "0px";
+       n.style.margin = "0px";
+       d.appendChild(n);
+       try {
+               scrollIntoView(d.lastChild);
+       }
+       catch (e) {}
+}
+
+function get_time() {
+       var d = new Date();
+       return d.getTime();
+}
+
+/* evaluates json response and returns object containing information */
+function evaluate_json(txt) {
+       var obj = new Object();
+
+       /* limit context */
+       obj = eval(txt, obj);
+       return obj;
+}
+
+function browser_is_opera() {
+       return window.opera?true:false;
+}
+function browser_is_konqueror() {
+       if(navigator.vendor=="KDE")
+               return true;
+       return false;
+}
+
+// replaces rem with ins
+function replace_node(ins, rem) {
+       return rem.parentNode.replaceChild(ins, rem);
+}
+// removes this nodes children
+function remove_children(what) {
+       if(!what.hasChildNodes)
+               return null;
+       var buf = create_fragment();
+       while(what.hasChildNodes()) 
+               buf.appendChild(what.removeChild(what.firstChild));
+       return buf;
+}
+
+function remove_node(what) {
+       if(what)
+               return what.parentNode.removeChild(what);
+       return null;
+}
+
+function remove_children_after(container, after) {
+       /* removing elements outside list */
+       var c = container.childNodes;
+       if(after>=c.length)
+               return;
+       var node = c[parseInt(after)];
+       while(node) {
+               var t = node.nextSibling;
+               container.removeChild(node);
+               node = t;
+       }
+}
+
+/* insert 'node' first on 'at' */
+function insert_first(node, at) {
+       if(at.hasChildNodes())
+               at.insertBefore(node, at.firstChild);
+       else at.appendChild(node);
+}
+
+function insert_before(what, before) {
+       before.parentNode.insertBefore(what, before);
+}
+
+function insert_after(what, after) {
+       var p = after.parentNode;
+       if(after.nextSibling)
+               p.insertBefore(what, after.nextSibling);
+       else p.appendChild(what);
+}
+
+function get_node_content(node) {
+
+       if(node.hasChildNodes && node.hasChildNodes())
+               node = node.childNodes[0];
+
+       if(node.nodeValue) {
+               return node.nodeValue;
+       }
+       else if(node.textContent) {
+               return node.textContent;
+       }
+       else if(node.textValue) {
+               return node.textValue;
+       }
+       else if(node.text) {
+               return node.text;
+       }
+       else {
+               return node;
+       }
+}
+
+/*Returns content of first node with specified tag
+ */
+function get_tag(item, tag) {
+       if(item.getElementsByTagName) {
+               var tmp = item.getElementsByTagName(tag)[0];
+               if(tmp!=null)
+                       return get_node_content(tmp);
+       }
+       return null;
+}
+
+function get_absolute_top(node) {
+       var height = 0; 
+       while(node.offsetParent != null)  {
+               node = node.offsetParent;
+               if(node.offsetTop)
+                       height += node.offsetTop;
+       }
+       return height;
+}
+
+// returns the absolute distance to start of page
+function get_absolute_height(node) {
+       return node.offsetTop + get_absolute_top(node); 
+}
+
+function get_absolute_left(node) {
+       var left = node.offsetLeft; 
+       while(node.offsetParent != null)  {
+               node = node.offsetParent;
+               if(node.offsetLeft) {
+                       left += node.offsetLeft;
+               }
+       }
+       return left;
+}
+
+function EventListener(to, type, func) {
+       this.to = to;
+       this.type = type;
+       this.func = func;
+}
+
+EventListener.prototype.register = function() {
+       if( this.to.addEventListener ) {
+               this.to.addEventListener(this.type, this.func, false);
+       }
+       else if(this.to.attachEvent) {
+               this.to.attachEvent(this.type, this.func);
+       }
+       else debug("unable to add listener");
+}
+
+EventListener.prototype.unregister = function() {
+       if(this.to.removeEventListener) {
+               this.to.removeEventListener(this.type, this.func, false); 
+       }
+       else if(to.detachEvent) {
+               this.to.detachEvent(this.type, this.func);
+       }
+       else debug("unable to detach event");
+}
+
+/**
+ * Creates a new event listener, register it and return it
+ */
+function add_listener(to, event_s, func) {
+       var el = new EventListener(to, event_s,func);
+       el.register();
+       return el;
+}
+
+function remove_listener(to, event_s, func) {
+       if(to.removeEventListener) {
+               to.removeEventListener(event_s, func, false); 
+       }
+       else if(to.detachEvent) {
+               to.detachEvent(event_s, func);
+       }
+       else debug("unable to detach event");
+}
+
+/* Get's first real child (e.g. one with an id
+ * from the element specified */
+function get_first_real_child(from) {
+       from = from.firstChild;
+       while(from.nextSibling) {
+               if(from.id)
+                       return from;
+               from = from.nextSibling;
+       }
+       return null;
+}
+
+function get_last_real_child(from) {
+       var children = from.childNodes;
+       for(var i=children.length-1; i>=0; i++) {
+               if(children[i].id) 
+                       return children[i];
+       }
+       return null;
+}
+
+
+/*
+ * Operations for creating elements
+ */ 
+// creates node of type type with id id and content content
+// returns the node created
+function create_node(type, id, content) {
+       var node = document.createElement(type);
+       if(id&&id!=null) {
+               node.id = id;
+               node.name = id;
+       }
+       if(content&&content!=null) {
+               if(document.createTextNode) 
+                 node.appendChild(document.createTextNode(content));
+               else
+                 node.innerHTML = content;
+       }
+       return node;
+}
+
+function create_txt(txt) {
+       return document.createTextNode(txt);
+}
+
+function create_fragment() {
+       return document.createDocumentFragment();
+}
+
+// creates an empty table row with id and
+// returns it
+function create_tr(id) {
+       return create_node("tr", id, null);
+}
+
+function create_param(name, value) {
+       var p = create_node("param");
+       p.name = name;
+       p.value = value;
+       return p;
+}
+
+// add a new TD with id id and content to a tr 
+function add_td(tr, content, id) {
+       var n = create_node("td", id, content);
+       tr.appendChild(n);
+       return n;
+}
+
+function add_li(list, content, id) {
+       var n = create_node("li", id, content);
+       list.appendChild(n);
+       return n;
+}
+
+function add_txt(node, txt) {
+       node.appendChild(create_txt(txt));
+}
+
+function apply_border_style_to_children(who, style) {
+       var c = who.childNodes;
+       for(var i=0; i<c.length; i++) {
+               if(c[i].style) {
+                       c[i].style.borderBottom = style;
+                       c[i].style.borderTop = style;
+               }
+       }
+}
+
+function MoveObject(container, on_change, multi_move) {
+       // container, moving, element that's moving, initial position, element that's simulating movement,
+       // function to call if something has been moved and a variable to know if mouse key is down, 
+       // if we should be able to move thigns around, optional doubleclick handler, optional selection change handler,
+       // if we *think* something is selected or not
+       this.container = container;     // container for the elements
+       this.on_change = on_change;     // function to call if something has been moved
+       this.moving = false;            // if we are moving something
+       this.moving_elem = null;        // the elemnt that is moving
+       this.moving_init_pos = null;    // initial position of moving element (pagex,pagey)
+       this.moving_clone = null;       // the clone that is actually moving
+       /* this is for interaction between mousemoving and mousedown */
+       this.possible_move_node = null; // the node the user might try moving
+       this.can_move = true;           // if we are allowed to move anything at all
+       this.double_click_cb = null;    // function to call on doubleclick
+       this.selection_change_cb = null;// function to call when the selection changes (with true or false 
+       this.select_if_no_move = false; // if nothing has moved the node should be selected
+       this.multi_move = multi_move;   // if allow for multiple move we won't do any of the moving and this function should
+                                       // return text that should be placed on the "moving" object
+       this.something_selected = false; // whether something currently is selected
+}
+
+/* visual effects 0> moving */
+
+function setup_node_move(container, on_change, multi_move) {
+       var id = moveables.length;
+       add_listener(container, "mousedown", mouse_down_node);
+       add_listener(container, "mouseup", mouse_up_node);
+       add_listener(container, "mousemove", move_node);
+       container.setAttribute("move_id", id);
+       if(!multi_move)
+               multi_move = false;
+
+       moveables[id] = new MoveObject(container, on_change, multi_move);
+       return id;
+}
+
+function add_move_doubleclick(id, func) {
+       moveables[id].double_click_cb = func;
+}
+
+function set_moveable(id, moveable) {
+       moveables[id].can_move = moveable;
+}
+
+function set_selection_change_handler(id, func) {
+       moveables[id].selection_change_cb = func;
+}
+
+/* NodeSelection _ find out if a node is selected */
+function is_node_selected(node) {
+       return node.hasAttribute&&node.hasAttribute("selected");
+}
+
+/* NodeSelection - set whether a node is selectable or not */
+function set_node_selectable(node, val) {
+       if(val&&node.hasAttribute&&node.hasAttribute("noselect")) {
+               node.removeAttribute("noselect");
+       }
+       else node.setAttribute("noselect", "true");
+}
+
+/* NodeSelection - select node */
+function select_node(node) {
+       if(node.hasAttribute("noselect"))
+               return false;
+       if(node.hasAttribute("selected"))
+               return true;
+
+       node.setAttribute("selected", "1");
+       return true;
+}
+
+function unselect_node(node) {
+       if(node.hasAttribute("selected")) {
+               node.removeAttribute("selected");
+       }
+}
+
+function unselect_all_nodes(container) {
+       if(xpath_ok()) {
+               var nodes = xpath_query(container, ".//.[@selected]");
+               var n; 
+               var elems = new Array();
+               while((n = nodes.iterateNext())) {
+                       elems[elems.length] = n;
+               }
+               for(var i=0; i<elems.length; i++) 
+                       unselect_node(elems[i]);
+       }
+       else {
+               var node = container.childNodes
+               if(!node)
+                       return;
+               for(var i=0; i < node.length; i++)  {
+                       if(node[i].hasAttribute("selected"))
+                               unselect_node(node[i]);
+               }
+       }
+}
+
+/* will check if anything is selected */
+function selection_anything_selected(container) {
+       if(xpath_ok()) {
+               var x = xpath_query(container, ".//.[@selected]", XPathResult.ANY_UNORDERED_NODE_TYPE);
+               return x.singleNodeValue?true:false;
+       }
+       else {
+               var nodes = container.childNodes
+               for(var i=0; i < nodes.length; i++)  {
+                       if(node.hasAttribute("selected"))
+                               return true;
+               }
+               return false;
+       }
+       
+}
+
+/* Will find the first selected node and set attribute needle
+ *  in needle if it is found before it returns
+ */
+function find_first_selected_node(container, needle) {
+       if(xpath_ok()&&!needle) {
+               var x = xpath_query(container, ".//.[@selected]", XPathResult.FIRST_ORDERED_NODE_TYPE);
+               return x.singleNodeValue;
+       }
+       else {
+               var nodes = container.childNodes
+               var length = nodes.length;
+               for(var i=0; i < length; i++)  {
+                       var node = nodes[i];
+                       if(needle&&needle==node)
+                               needle.setAttribute("needle", "found");
+                       if(node.hasAttribute&&node.hasAttribute("selected"))
+                               return node;
+               }
+               return null;
+       }
+}
+
+/* selects all nodes between the two */
+function select_range(from, to) {
+       var node = from; 
+       select_node(from);
+       while(node != null && node!=to) {
+               node = node.nextSibling;
+               select_node(node);
+       } 
+}
+
+/* will return an array of selected elements attribute in container */
+function get_attribute_from_selected_elems(container, attribute) {
+       var c = container.childNodes;
+       var ret = new Array();
+       var l = c.length;
+       for(var i=0; i<l; i++) {
+               if(is_node_selected(c[i]) && c[i].hasAttribute(attribute)) {
+                       ret[ret.length] = c[i].getAttribute(attribute);
+               }
+       }
+       return ret;
+}
+
+function mouse_down_node(e) {
+       if(e.button!=0&&e.button!=1)
+               return;
+       
+       move_idx = get_target_id(e.target);
+
+       if(move_idx<0) 
+               return;
+       stop_event(e);
+
+       var m = moveables[move_idx];
+       var elem = find_target_node(e.target);
+       if(elem==null)
+               return;
+       /* move_node will toggle this if true and call start_node_move to initiate moving*/
+       // do not move if it specified not to move
+       if(m.can_move) {
+               m.moving_init_pos = new Array(e.pageX, e.pageY);
+               m.possible_move_node = elem;
+       }
+       var something_selected = true;
+       if(e.detail>1) { // we were just here, don't do the other stuff again..
+               /* double click */
+               if(m.double_click_cb&&e.detail==2) 
+                       m.double_click_cb(elem, e);
+       }
+       else if(e.ctrlKey||e.metaKey) {
+               if(is_node_selected(elem)) {
+                       unselect_node(elem);
+                       if(m.selection_change_cb&&m.something_selected)
+                               something_selected = selection_anything_selected(m.container);
+               }
+               else {
+                       select_node(elem);
+               }
+       }
+       else if (e.shiftKey) {
+               var sel_node = null; 
+               
+               sel_node = find_first_selected_node(m.container, elem);
+               if(sel_node==null) {
+                       select_node(elem);
+               }
+               else {
+                       if(elem.hasAttribute("needle")) {
+                               select_range(elem, sel_node);
+                               elem.removeAttribute("needle");
+                       }
+                       else {
+                               select_range(sel_node, elem);
+                       }
+               }
+       }
+       else {
+               if(!is_node_selected(elem)) {
+                       unselect_all_nodes(m.container);
+                       select_node(elem);
+               }
+               else {
+                       m.select_if_no_move = true;
+               }
+       }
+       /* something selected */
+       if(m.selection_change_cb) {
+               m.selection_change_cb(something_selected);
+               m.something_selected = something_selected;
+       }
+}
+
+function mouse_up_node(e) {
+       if(move_idx<0)
+               return;
+       var m = moveables[move_idx];
+       if(m.moving) {
+               stop_node_move(e);
+       }
+       else if(m.select_if_no_move) {
+               var elem = find_target_node(e.target);
+               unselect_all_nodes(m.container);
+               select_node(elem);
+       }
+       m.select_if_no_move = false;
+       m.possible_move_node = null;
+       // safari workaround
+       m.container.className = m.container.className;
+}
+
+/* todo; rework to use elem instead of event */
+function start_node_move(e, elem) {
+       stop_event(e);
+
+       move_idx = get_target_id(e.target);
+
+       if(move_idx<0)
+               return;
+       if(!moveables[move_idx].can_move)
+               return;
+
+       var m = moveables[move_idx];
+       var move = find_target_node(e.target);
+       var container = m.container;
+       if(move!=null) {
+               m.moving = true; // moving 
+               m.moving_elem = find_target_node(m.possible_move_node); // what
+               move = m.moving_elem;
+               m.possible_move_node = null;
+               var txt = "Moving";
+               if(m.multi_move) 
+                       txt = m.multi_move();
+               else if(move.childNodes[1])
+                       txt = move.childNodes[1].textContent; 
+               else txt = move.childNodes[0].textConten;
+
+               m.moving_clone = detach_node(move, txt);
+               set_node_offset(e, m.moving_clone);
+
+               add_listener(document.body, "mouseup", stop_node_move);
+               add_listener(document.body, "mousemove", move_node);
+
+               container.style.cursor = "move";
+       }
+       else move_idx = -1; 
+}
+
+
+/* basically the reverse of start */
+function stop_node_move(e) {
+       if(move_idx<0||!moveables[move_idx].moving)
+               return;
+
+       var m = moveables[move_idx];
+       m.moving = false;
+       var move = m.moving_elem;
+       var container = m.container;
+       var target = find_target_node(e.target);
+
+       if(m.multi_move) {
+               /* don't move it if we are moving on top of selection */
+               if(target&&!is_node_selected(target)) {
+                       m.on_change(null,target);
+               }
+               reattach_node(move, null, m.moving_clone, 4); // remove moving node
+       }
+       else if(target!=null&&target!=move) {
+               /* if first in list or above the one beeing moved, move it up */
+               var to = null;
+               if(target==get_first_real_child(container)||target.nextSibling==move) {
+                       to = target;
+                       reattach_node(move, target, m.moving_clone, 1);
+               }
+               else if(target.nextSibling!=null) {
+                       /* normally default action */
+                       var attach_at;
+                       to = target;
+                       if(is_moving_up(container, move, target)) {
+                               attach_at = target;
+                       }
+                       else {
+                               attach_at = target.nextSibling;
+                       }
+                       reattach_node(move, attach_at, m.moving_clone, 1);
+               }
+               else {
+                       /* basically this means we don't know any better */
+                       /* should not happen unless target actually is last in list */
+                       /*to = get_last_real_child(container);
+                       reattach_node(move, container, m[4], 2);*/
+                       to = target;
+                       reattach_node(move, container, m.moving_clone, 2);
+               }
+
+               // say we changed
+               m.on_change(null, to);
+       }
+       else {
+               reattach_node(move, null, m.moving_clone, 4); // don't move it
+       }
+
+       container.style.cursor = "default";
+
+       remove_listener(document.body, "mouseup", stop_node_move);
+       remove_listener(document.body, "mousemove", move_node); 
+
+       m.moving_elem = null;
+       m.moving_init_pos = null;
+       m.moving_clone = null;
+       move_idx = -1; 
+}
+
+function move_node(e) {
+       var id = window.move_idx || -1;
+       var o = 4; // required offset
+       if(id&&id>=0) { 
+               if(moveables[id].possible_move_node!=null) {
+                       var p = moveables[id].moving_init_pos;
+                       if(Math.abs(p[0]-e.pageX)>=o||Math.abs(p[1]-e.pageY)>o)
+                               start_node_move(e);
+               }
+               if(!moveables[id].moving)
+                       return;
+
+               stop_event(e);
+               set_node_offset(e, moveables[id].moving_clone);
+       }
+}
+
+function is_moving_up(container, move, target) {
+       var c = container.childNodes;
+       for(var i=0; i<c.length; i++) {
+               if(move==c[i])
+                       return false;
+               if(target==c[i])
+                       return true;
+       }
+       debug("Wops, not moving up or down!")
+       return false;
+}
+
+
+function detach_node(d, txt) {
+       var rep = create_node("div");
+       
+       rep.style.width = d.offsetWidth/4 + "px";
+       rep.className = "moving_box";
+
+       txt = create_node("p", null, txt);
+       txt.className = "nomargin";
+       rep.appendChild(txt);
+
+       d.setAttribute("old_class", d.className);
+       d.className = "moving";
+
+       document.body.appendChild(rep);
+       return rep;
+}
+ /* reattach node at specified position (at) with action
+ *  1 => insertBefore
+ *  2 => appendChild
+ *  3 => replaceChild
+ *  4 => dont move
+ */
+function reattach_node(node, at, clone, action) {
+       node.style.width =""; 
+       node.style.top = "";
+       node.style.position = "";
+       node.className = node.getAttribute("old_class");
+       if(action==1) {
+               remove_node(node);
+               at.parentNode.insertBefore(node, at);
+       }
+       else if(action == 2) {
+               remove_node(node);
+               at.appendChild(node); 
+       }
+       else if(action == 3) {
+               remove_node(node);
+               replace_node(node, at);
+       }
+       else if(action==4)  { 
+       }
+       else {
+               debug("invalid action in reattach_node");
+       }
+       remove_node(clone);
+}
+
+function get_target_id(target) {
+       var t = find_target_node(target);
+       if(t!=null&&t.parentNode&&t.parentNode!=null) 
+               return t.parentNode.getAttribute("move_id");
+       else return -1;
+}
+
+function find_target_node(target) {
+       while(target != null && target.parentNode&&target.parentNode != null) {
+         for(var i=0; i<moveables.length; i++) 
+           if(moveables[i].container==target.parentNode)
+             return target;
+         target = target.parentNode;
+       }
+       return null;
+}
+
+/* set's this node to the position in event */
+function set_node_offset(ev, node) {
+       /* relative positioning:*/
+       var ot = 0; 
+       var ol = 0; 
+       if(node.hasAttribute("ot")&&node.hasAttribute("ol")) {
+               ot = node.getAttribute("ot");
+               ol = node.getAttribute("ol");
+       }
+       else {
+               ot = node.offsetTop; 
+               ol = node.offsetLeft - 10;
+               node.setAttribute("ot", ot);
+               node.setAttribute("ol", ol); 
+       }
+       var h = ev.pageY - ot; 
+       var l = ev.pageX - ol;
+       /* absolute: 
+       var h = ev.pageY - (node.offsetHeight/2);
+       var l = ev.pageX + 10;*/ 
+       node.style.top = h + "px";
+       node.style.left = l + "px";
+       return h;
+}
+
+function Slider(sid, main_slider, change, text_area) {
+       this.main_slider = main_slider;
+       this.change_call = change;
+       this.text_area = text_area;
+       this.value = 0;
+       this.moving_pos = 0;
+       this.user_moving = false;
+       this.timer = null;
+       this.timer_last_pos = null;
+       this.sid = sid;
+}
+
+Slider.prototype.setup_timer = function() {
+       if(this.timer!=null)
+               clearTimeout(this.timer);
+       this.timer_last_pos = this.moving_pos;
+       this.timer = setTimeout(this.timer_cb, 250);
+}
+Slider.prototype.timer_cb = function() {
+       // user has managed to hold mousepoitner stil
+       if(this.timer_last_pos==this.moving_pos) {
+               // omg...
+               var idx = window.slider_idx;
+               window.slider_send_callback(idx);
+       }
+       else if(this.user_moving) {
+               this.setup_timer();
+       }
+}
+
+/* should be a div */
+function setup_slider(slider, callback, txt) {
+       var sid = sliders.length;
+       if(txt!=null) {
+               txt = create_node("p", "slider_txt_" + sid, txt);
+               txt.className = "slider_txt";
+               slider.appendChild(txt);
+       }
+       var s = create_node("div");
+       s.className = "slider_main";
+       s.id = "slider_main" + sid;
+       slider.appendChild(s);
+       var pointer = create_node("div", " ");
+       pointer.className = "slider_pointer";
+       pointer.id = "slider_pointer" + sid;
+       s.setAttribute("sliderid", sid);
+       add_listener(s, "mousedown", mouse_down_slider);
+       pointer.style.height = (s.offsetHeight) + "px";
+       s.appendChild(pointer);
+       sliders[sid] = new Slider(sid, slider, callback, txt);
+       set_slider_pos(sid, 0);
+       return sid;
+}
+
+function get_slider_txt(sid) {
+       return sliders[sid].text_area;
+}
+
+function slider_send_callback(sid) {
+       if(sliders[sid].change_call)
+               sliders[sid].change_call(sliders[sid].value);
+}
+
+function set_slider_pos(sid, pos, force) {
+       var pointer = document.getElementById("slider_pointer" + sid);
+       var s = document.getElementById("slider_main" + sid);
+       if(!force)
+               force = false;
+       if(pointer==null||s==null) {
+               debug("no slider pointer||main");
+               return;
+       }
+
+       if(sliders[sid].user_moving&&!force)
+               return;
+
+       if(pos>100)
+               pos=100;
+       if(pos<0)
+               pos=0;
+
+       if(pos==sliders[sid].value) {
+               return;
+       }
+
+       sliders[sid].value = pos;
+
+       var dist = (s.offsetWidth * pos) /100
+       if(isNaN(dist)||dist=="NaN")
+               dist = 0;
+       
+       pointer.style.left =  dist+ "px";
+}
+
+function get_slider_pos(sid) {
+       return sliders[sid].value;
+}
+
+function mouse_down_slider(e) {
+       var targ = e.target;
+       /* TODO: rewrite test */
+       if(!targ||!targ.hasAttribute||!targ.hasAttribute("sliderid")) {
+               if(targ) 
+                       targ = targ.parentNode;
+               // *umpf*
+               if(!targ||!targ.hasAttribute||!targ.hasAttribute("sliderid")) 
+                       return;
+       }
+       
+       slider_idx = targ.getAttribute("sliderid");
+
+       add_listener(document.body, "mousemove", mouse_move_slider);
+       add_listener(document.body, "mouseup", mouse_up_slider);
+
+       sliders[slider_idx].user_moving = true;
+       sliders[slider_idx].main_slider.setAttribute("slider_moving", "yeahitis");
+
+       mouse_move_slider(e); // lazy
+       //e.stopPropagation();
+
+}
+
+function mouse_move_slider(e) {
+       if(slider_idx<0) {
+               debug("mouse_move_slider should not be called now");
+               mouse_up_slider(e);
+               return;
+       }
+       stop_event(e);
+       var left = slider_get_left(e, slider_idx)
+       set_slider_pos(slider_idx, left, true);
+       sliders[slider_idx].moving_pos = left;
+       sliders[slider_idx].setup_timer(sliders[slider_idx].change_call);
+}
+
+function mouse_up_slider(e) {
+       if(slider_idx<0)
+               return;
+
+       // prolly not necessary though
+       clearTimeout(sliders[slider_idx].timer);
+       sliders[slider_idx].timer = null;
+
+       remove_listener(document.body, "mousemove", mouse_move_slider);
+       remove_listener(document.body, "mouseup", mouse_up_slider);
+       sliders[slider_idx].user_moving = false;
+       sliders[slider_idx].main_slider.removeAttribute("slider_moving");
+       slider_send_callback(slider_idx);
+       slider_idx = -1;
+}
+
+function slider_get_left(e, sid) {
+       var x = e.pageX - get_absolute_left(sliders[sid].main_slider);
+       x = (x*100)/sliders[sid].main_slider.offsetWidth;
+       x-=3;
+       if(x<0)
+               x = 0;
+       return x;
+}
+
+function OverlayObject(back, sizes, open_callback, close_callback) {
+       this.back = back; // element to put overlay over
+       this.sizes = sizes; // minimum sizes [top, left, min-height, min-width ]
+       this.open_callback = open_callback;
+       this.close_callback = close_callback;
+       this.overlay = null; // the overlay element
+       this.write = null; // write area
+}
+
+/* overlay */
+function setup_overlay(back, sizes, open_callback, close_callback) {
+       var oid = overlay.length;
+       overlay[oid] = new OverlayObject(back, sizes, open_callback, close_callback); 
+       var t = create_node("div", "overlay_" + oid);
+       overlay[oid].overlay = t;
+       t.className = "overlay";
+       t.style.height = overlay_adjust + "px";
+       var img = create_node("img", "overlay_close_" + oid);
+       img.src = IMAGE.CLOSE;
+       img.setAttribute("oid", oid);
+       img.className = "close fakelink";
+       img.title = "Close [Ctrl+Shift+X]";
+       add_listener(img, "click", close_overlay_cb);
+
+       t.appendChild(img);
+
+       document.body.appendChild(t);
+
+       return oid;
+}
+
+function get_overlay_write_area(oid) {
+       if(overlay[oid].write==null) {
+               overlay[oid].write = create_node("div", "overlay_write_area_" + oid);
+               overlay[oid].overlay.appendChild(overlay[oid].write);
+
+       }
+       return overlay[oid].write;
+}
+
+function open_overlay(oid) {
+       var sizes = overlay[oid].sizes;
+       var o = overlay[oid].back;
+       var top = get_absolute_top(o);
+       if(top<sizes[0])
+               top = sizes[0];
+       var left = get_absolute_left(o);
+       if(left<sizes[1])
+               left = sizes[1];
+       var height = o.offsetHeight;
+       if(height<sizes[2])
+               height = sizes[2];
+       var width = o.offsetWidth;
+       if(width<sizes[3])
+               width = sizes[3];
+
+       if(overlay_hide_on_resize&&overlay[oid].write)
+               overlay[oid].write.style.display = "none";
+       
+       var op = overlay[oid].overlay;
+       open_overlay_idx = oid;
+       op.style.left = left + "px";
+       op.style.top = top + "px";
+       op.style.width = overlay_adjust + "px";
+       op.style.height = overlay_adjust + "px";
+
+       op.style.display = "block";
+       var hx = 1, wx =1;
+       if(width>height) 
+               wx = width/height;
+       else 
+               hx = height/width;
+
+       overlay[oid].close_key = keyboard_register_listener(close_overlay_cb, "x", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true);
+
+       setTimeout(adjust_overlay_size, overlay_time, oid, new Array(overlay_adjust, overlay_adjust), new Array(height, width, hx, wx));
+}
+
+function open_overlay_fixed(oid) {
+       // TODO: find another way to determine these heights
+       var sizes = new Array(106, 56, 800, 500); 
+
+       var height = sizes[3];
+       var width = sizes[2];
+       var op = overlay[oid].overlay;
+       open_overlay_idx = oid;
+       if(overlay_hide_on_resize&&overlay[oid].write)
+               overlay[oid].write.style.display = "none";
+       
+       op.style.position = "fixed";
+
+       op.style.left = sizes[1] + "px";
+       op.style.top = sizes[0] + "px";
+       op.style.width = overlay_adjust + "px";
+       op.style.height = overlay_adjust + "px";
+
+       op.style.display = "block";
+
+       /* adjust to browser window */
+       var x_o = 30;
+       var w_h = window.innerHeight; 
+       var w_w = window.innerWidth; 
+
+       /* ignore it if unreasonable values.. */
+       if(w_h&&w_w&&w_h>100&&w_w>100) {
+               if(height+sizes[0]+x_o>w_h)
+                       height = w_h - sizes[0] - x_o;
+               if(width+sizes[1]+x_o>w_w)
+                       width = w_w - sizes[1] - x_o;
+       }
+
+       var hx = 1, wx =1;
+       if(width>height) 
+               wx = width/height;
+       else 
+               hx = height/width;
+
+       overlay[oid].close_key = keyboard_register_listener(close_overlay_cb, "x", KEYBOARD_CTRL_KEY|KEYBOARD_SHIFT_KEY, true);
+
+       setTimeout(adjust_overlay_size, overlay_time, oid, new Array(overlay_adjust, overlay_adjust), new Array(height, width, hx, wx));
+}
+
+function adjust_overlay_size(oid, current, dest) {
+       var h = current[0] = current[0] + (dest[2]*overlay_adjust);
+       var w = current[1] = current[1] + (dest[3]*overlay_adjust);
+       var adjusted = false;
+
+       if(h<dest[0]) {
+               adjusted = true;
+       }
+       else { 
+               h = dest[0];
+       }
+       if(w<dest[1]) {
+               adjusted = true
+       }
+       else {
+               w = dest[1];
+       }
+       h = parseInt(h);
+       w = parseInt(w);
+       overlay[oid].overlay.style.height = h + "px";
+       overlay[oid].overlay.style.width = w + "px";
+       //debug("h: " + h + ", w: " + w);
+
+       if(adjusted) {
+               //debug("setting timeout");
+               setTimeout(adjust_overlay_size, overlay_time, oid, current, dest);
+       }
+       else {
+               var height = (overlay[oid].overlay.offsetHeight-20);
+               if(overlay[oid].write) {
+                       if(overlay_hide_on_resize) {
+                               overlay[oid].write.style.display = "block"; // kiss
+                       }
+               }
+               if(overlay[oid].open_callback) 
+                       overlay[oid].open_callback(height);
+       }
+}
+
+function close_overlay(oid) {
+       var o = overlay[oid].overlay;
+       open_overlay_idx = -1;
+       o.style.display = "none";
+       if(overlay[oid].close_key) 
+               overlay[oid].close_key = keyboard_remove_listener(overlay[oid].close_key);
+       if(overlay[oid].close_callback) 
+               overlay[oid].close_callback();
+}
+function close_overlay_cb(e) {
+       var t = e.target;
+       if(t&&t.hasAttribute&&t.hasAttribute("oid")) {
+               close_overlay(t.getAttribute("oid"));
+               stop_event(e);
+       }
+       else if(open_overlay_idx>=0) {
+               close_overlay(open_overlay_idx);
+               stop_event(e);
+       }
+       
+}
+
+function stop_propagation(e) {
+       if(e.stopPropagation)
+               e.stopPropagation();
+}
+
+function stop_event(e) {
+       if(e) {
+               if(e.preventDefault) 
+                       e.preventDefault();
+               if(e.stopPropagation)
+                       e.stopPropagation();
+               if(e.returnValue) 
+                       e.returnValue = false;
+       }
+}
+
+/* range selection (to put ranges in a "list" */
+/* txt: excisting range:
+ * from: from what number
+ * to: optional to argument
+ */
+function add_range(txt, from, to) {
+       if(txt.length>0)
+               txt+=";";
+       txt+=from;
+       if(to)
+               txt+="-" + to;
+       return txt;
+}
+
+function scrollIntoView(elem, top) {
+       if(!top)
+               top = false;
+       /* seriously though, if you don't support it, don't claim you do!*/
+       //if(elem.scrollIntoView) {
+       if(navigator.product&&navigator.product=="Gecko") {
+               elem.scrollIntoView(top);
+       }
+       else if(elem.parentNode) {
+               // TODO: top
+               try { 
+                       elem.parentNode.scrollTop=elem.offsetTop - (top?elem.offsetHeight*2:elem.parentNode.offsetHeight);
+               } catch (e) { }
+       }
+}
+
+function setSelectionRange(input, selectionStart, selectionEnd) {
+  if (input.setSelectionRange) {
+    input.focus();
+    input.setSelectionRange(selectionStart, selectionEnd);
+  }
+  else if (input.createTextRange) {
+    var range = input.createTextRange();
+    range.collapse(true);
+    range.moveEnd('character', selectionEnd);
+    range.moveStart('character', selectionStart);
+    range.select();
+    range.detach();
+  }
+}
+function setCaretToEnd (input) {
+  setSelectionRange(input, input.value.length, input.value.length);
+}
+function setCaretToBegin (input) {
+  setSelectionRange(input, 0, 0);
+}
+function setCaretToPos (input, pos) {
+  setSelectionRange(input, pos, pos);
+}
+
+
+
+String.prototype.trim = function() {
+       return this.replace(/^\s+|\s+$/g,'');
+}
+
+function add_string_with_br(to, str) {
+       str = str.replace("\r", "").split('\n');
+       for(var i=0; i<str.length; i++) {
+               if(str[i].length>0)
+                       to.appendChild(create_txt(str[i]));
+               to.appendChild(create_node("br"));
+       }
+}
+
+function adjust_opacity_timer(node, current_opacity, dest_opacity, last_time) {
+       var now = get_time(); 
+       var time = now;
+       if(last_time) 
+               time -= last_time;
+       else time = 0;
+       
+       time = 100-time;
+       //debug("time: " + time);
+       if(time<0) {
+               time = 0;
+               current_opacity+=0.2;
+       }
+       else {
+               current_opacity+=0.1;
+       }
+
+       if(current_opacity<dest_opacity) {
+               node.style.opacity = current_opacity ;
+               setTimeout(adjust_opacity_timer, time, node, current_opacity, dest_opacity, now);
+       }
+       else {
+               node.style.opacity = dest_opacity ;
+       }
+}
+
+/* what to blink, what color and count, two first arguments are required */
+function blink_node(what, color, count) {
+       if(typeof(count)=='undefined') {
+               count = 3;
+       }
+       if(count%2==1)
+               what.style.backgroundColor = color;
+       else 
+               what.style.backgroundColor = "";
+       if(count>0) {
+               setTimeout(blink_node, 350, what, color, --count);
+       }
+}
+
+/* popup */
+/* if content is null, we'll use tabs */
+function Popup(point, content) {
+       this.point = point;
+       this.content = content;
+       this.popup = create_node("div");
+       if(content)
+               this.popup.appendChild(content);
+       this.popup.className = "popup";
+       this.point.appendChild(this.popup);
+}
+
+Popup.prototype.show = function() {
+       this.popup.style.display = "block";
+}
+Popup.prototype.hide = function() {
+       this.popup.style.display = "";
+}
+Popup.prototype.destroy = function() {
+       remove_node(this.popup);
+       this.popup = null;
+       this.point = null;
+       this.content = null;
+}
+
+/* xpath */
+function xpath_init() {
+       xpath_queries = new Hashtable();
+}
+
+/* checks if xpath is available */
+function xpath_ok() {
+       return document.evaluate?true:false;
+}
+
+// remember to check with xpath_ok first when using this function
+function xpath_query(container, expression, resulttype, nocache_query) {
+       if(!resulttype)
+               resulttype = XPathResult.ANY_TYPE;
+       if(nocache_query) {
+               return document.evaluate(expression, container, null, resulttype, null);
+       }
+       else {
+               var e = xpath_queries.get(expression);
+               if(!e) {
+                       e = document.createExpression(expression, null);
+                       xpath_queries.put(expression, e);
+               }
+               return e.evaluate(container, resulttype, null); 
+       }
+}
+
+function opera_quirk_set_display_none(element, cleanup) {
+       if(cleanup) {
+               element.style.display = "none";
+               element.style.visibility = "";
+       }
+       else {
+               setTimeout(opera_quirk_set_display_none, 10, element, true);
+               element.style.visibility = "hidden";
+       }
+}
+
+function createCookie(name,value,days) {
+       if (days) {
+               var date = new Date();
+               date.setTime(date.getTime()+(days*24*60*60*1000));
+               var expires = "; expires="+date.toGMTString();
+       }
+       else var expires = "";
+       document.cookie = name+"="+value+expires+"; path=/";
+}
+function readCookie(name) {
+       var nameEQ = name + "=";
+       var ca = document.cookie.split(';');
+       for(var i=0;i < ca.length;i++) {
+               var c = ca[i];
+               while (c.charAt(0)==' ') c = c.substring(1,c.length);
+               if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+       }
+       return null;
+}
+function eraseCookie(name) {
+       createCookie(name,"",-1);
+}
+
+// This function is in the public domain. Feel free to link back to http://jan.moesen.nu/
+function sprintf() {
+       if (!arguments || arguments.length < 1 || !RegExp) {
+               return "";
+       }
+       var str = arguments[0];
+       var re = /([^%]*)%('.|0|\x20)?(-)?(\d+)?(\.\d+)?(%|b|c|d|u|f|o|s|x|X)(.*)/;
+       var a = b = [], numSubstitutions = 0, numMatches = 0;
+       while ((a = re.exec(str))) {
+               var leftpart = a[1], pPad = a[2], pJustify = a[3], pMinLength = a[4];
+               var pPrecision = a[5], pType = a[6], rightPart = a[7];
+               
+               //alert(a + '\n' + [a[0], leftpart, pPad, pJustify, pMinLength, pPrecision);
+
+               numMatches++;
+               if (pType == '%') {
+                       subst = '%';
+               }
+               else {
+                       numSubstitutions++;
+                       if (numSubstitutions >= arguments.length) {
+                               alert('Error! Not enough function arguments (' + (arguments.length - 1) + ', excluding the string)\nfor the number of substitution parameters in string (' + numSubstitutions + ' so far).');
+                       }
+                       var param = arguments[numSubstitutions];
+                       var pad = '';
+                              if (pPad && pPad.substr(0,1) == "'") pad = leftpart.substr(1,1);
+                         else if (pPad) pad = pPad;
+                       var justifyRight = true;
+                              if (pJustify && pJustify === "-") justifyRight = false;
+                       var minLength = -1;
+                              if (pMinLength) minLength = parseInt(pMinLength);
+                       var precision = -1;
+                              if (pPrecision && pType == 'f') precision = parseInt(pPrecision.substring(1));
+                       var subst = param;
+                              if (pType == 'b') subst = parseInt(param).toString(2);
+                         else if (pType == 'c') subst = String.fromCharCode(parseInt(param));
+                         else if (pType == 'd') subst = parseInt(param) ? parseInt(param) : 0;
+                         else if (pType == 'u') subst = Math.abs(param);
+                         else if (pType == 'f') subst = (precision > -1) ? Math.round(parseFloat(param) * Math.pow(10, precision)) / Math.pow(10, precision): parseFloat(param);
+                         else if (pType == 'o') subst = parseInt(param).toString(8);
+                         else if (pType == 's') subst = param;
+                         else if (pType == 'x') subst = ('' + parseInt(param).toString(16)).toLowerCase();
+                         else if (pType == 'X') subst = ('' + parseInt(param).toString(16)).toUpperCase();
+               }
+               str = leftpart + subst + rightPart;
+       }
+       return str;
+}
+
+/* settings */
+
+function setting_set(name, value) {
+       var s = readCookie("pf_conf");
+       
+       var ns = "";
+       var set = false;
+       if(s) {
+               s = s.split(":");
+               for(var i=0; i< s.length; i++) {
+                       var tmp = s[i].split("-");
+                       if(!tmp[0].length)
+                               continue;
+
+                       if(tmp[0]==name) {
+                               ns+=name + "-" + value;
+                               set = true;
+                       }
+                       else {
+                               ns+=s[i];
+                       }
+                       ns+=":";
+               }
+       }
+       if(!set) 
+               ns+=name + "-" +value + ":";
+       
+       createCookie("pf_conf", ns, 200);
+
+       return true;
+}
+
+function setting_get(name) {
+       var val = readCookie("pf_conf");
+
+       if(!val||!val.length)
+               return null;
+
+       val = val.split(":");
+       for(var i=0; i < val.length; i++) {
+               var t = val[i].split("-");
+               if(t[0]==name)
+                       return t[1];
+       }
+       return null;
+}
diff --git a/theme/dark/images/COPYING b/theme/dark/images/COPYING
new file mode 100644 (file)
index 0000000..9bfb00e
--- /dev/null
@@ -0,0 +1,4 @@
+Most of these icons are from tango-noir: 
+http://www.gnome-look.org/content/show.php?content=38964
+License: 
+http://creativecommons.org/licenses/by-sa/2.0/
diff --git a/theme/dark/images/add_15.png b/theme/dark/images/add_15.png
new file mode 100644 (file)
index 0000000..3c1a500
Binary files /dev/null and b/theme/dark/images/add_15.png differ
diff --git a/theme/dark/images/audio_li.png b/theme/dark/images/audio_li.png
new file mode 100644 (file)
index 0000000..c1dddc2
Binary files /dev/null and b/theme/dark/images/audio_li.png differ
diff --git a/theme/dark/images/close.png b/theme/dark/images/close.png
new file mode 100644 (file)
index 0000000..5f30185
Binary files /dev/null and b/theme/dark/images/close.png differ
diff --git a/theme/dark/images/close_15.png b/theme/dark/images/close_15.png
new file mode 100644 (file)
index 0000000..15c022c
Binary files /dev/null and b/theme/dark/images/close_15.png differ
diff --git a/theme/dark/images/crop_30.png b/theme/dark/images/crop_30.png
new file mode 100644 (file)
index 0000000..ad81d80
Binary files /dev/null and b/theme/dark/images/crop_30.png differ
diff --git a/theme/dark/images/disabled_15.png b/theme/dark/images/disabled_15.png
new file mode 100644 (file)
index 0000000..e5f1919
Binary files /dev/null and b/theme/dark/images/disabled_15.png differ
diff --git a/theme/dark/images/enabled_15.png b/theme/dark/images/enabled_15.png
new file mode 100644 (file)
index 0000000..048efaf
Binary files /dev/null and b/theme/dark/images/enabled_15.png differ
diff --git a/theme/dark/images/folder_30.png b/theme/dark/images/folder_30.png
new file mode 100644 (file)
index 0000000..a02745a
Binary files /dev/null and b/theme/dark/images/folder_30.png differ
diff --git a/theme/dark/images/folder_li.png b/theme/dark/images/folder_li.png
new file mode 100644 (file)
index 0000000..62791a0
Binary files /dev/null and b/theme/dark/images/folder_li.png differ
diff --git a/theme/dark/images/follow-playing-no.orig.png b/theme/dark/images/follow-playing-no.orig.png
new file mode 100644 (file)
index 0000000..dd64ba8
Binary files /dev/null and b/theme/dark/images/follow-playing-no.orig.png differ
diff --git a/theme/dark/images/follow-playing-no.png b/theme/dark/images/follow-playing-no.png
new file mode 100644 (file)
index 0000000..3597926
Binary files /dev/null and b/theme/dark/images/follow-playing-no.png differ
diff --git a/theme/dark/images/follow-playing-yes.orig.png b/theme/dark/images/follow-playing-yes.orig.png
new file mode 100644 (file)
index 0000000..8c72f80
Binary files /dev/null and b/theme/dark/images/follow-playing-yes.orig.png differ
diff --git a/theme/dark/images/follow-playing-yes.png b/theme/dark/images/follow-playing-yes.png
new file mode 100644 (file)
index 0000000..5ec364d
Binary files /dev/null and b/theme/dark/images/follow-playing-yes.png differ
diff --git a/theme/dark/images/jump-to-current.orig.png b/theme/dark/images/jump-to-current.orig.png
new file mode 100644 (file)
index 0000000..5b742cf
Binary files /dev/null and b/theme/dark/images/jump-to-current.orig.png differ
diff --git a/theme/dark/images/jump-to-current.png b/theme/dark/images/jump-to-current.png
new file mode 100644 (file)
index 0000000..499ecae
Binary files /dev/null and b/theme/dark/images/jump-to-current.png differ
diff --git a/theme/dark/images/left_15.png b/theme/dark/images/left_15.png
new file mode 100644 (file)
index 0000000..4dc9bda
Binary files /dev/null and b/theme/dark/images/left_15.png differ
diff --git a/theme/dark/images/media-album_li.png b/theme/dark/images/media-album_li.png
new file mode 100644 (file)
index 0000000..a27323c
Binary files /dev/null and b/theme/dark/images/media-album_li.png differ
diff --git a/theme/dark/images/media-artist_li.png b/theme/dark/images/media-artist_li.png
new file mode 100644 (file)
index 0000000..c87c715
Binary files /dev/null and b/theme/dark/images/media-artist_li.png differ
diff --git a/theme/dark/images/next_25.png b/theme/dark/images/next_25.png
new file mode 100644 (file)
index 0000000..c9938dd
Binary files /dev/null and b/theme/dark/images/next_25.png differ
diff --git a/theme/dark/images/pause_25.png b/theme/dark/images/pause_25.png
new file mode 100644 (file)
index 0000000..7e40972
Binary files /dev/null and b/theme/dark/images/pause_25.png differ
diff --git a/theme/dark/images/play_25.png b/theme/dark/images/play_25.png
new file mode 100644 (file)
index 0000000..a2a15fb
Binary files /dev/null and b/theme/dark/images/play_25.png differ
diff --git a/theme/dark/images/playlist_li.png b/theme/dark/images/playlist_li.png
new file mode 100644 (file)
index 0000000..627a858
Binary files /dev/null and b/theme/dark/images/playlist_li.png differ
diff --git a/theme/dark/images/previous_25.png b/theme/dark/images/previous_25.png
new file mode 100644 (file)
index 0000000..e9e2527
Binary files /dev/null and b/theme/dark/images/previous_25.png differ
diff --git a/theme/dark/images/remove_30.png b/theme/dark/images/remove_30.png
new file mode 100644 (file)
index 0000000..5415bf1
Binary files /dev/null and b/theme/dark/images/remove_30.png differ
diff --git a/theme/dark/images/right_15.png b/theme/dark/images/right_15.png
new file mode 100644 (file)
index 0000000..e80309f
Binary files /dev/null and b/theme/dark/images/right_15.png differ
diff --git a/theme/dark/images/stop_25.png b/theme/dark/images/stop_25.png
new file mode 100644 (file)
index 0000000..9147847
Binary files /dev/null and b/theme/dark/images/stop_25.png differ
diff --git a/theme/dark/images/working.gif b/theme/dark/images/working.gif
new file mode 100644 (file)
index 0000000..6a19d8d
Binary files /dev/null and b/theme/dark/images/working.gif differ
diff --git a/theme/dark/theme.css b/theme/dark/theme.css
new file mode 100644 (file)
index 0000000..afa0fb9
--- /dev/null
@@ -0,0 +1,159 @@
+/* top right bottom left */
+body {
+       font-size: 12.0px;
+       font-family: "Bitstream Vera Sans","Verdana";
+       background-color: #0a0b0e;
+}
+
+select, input, a, body {
+       color: #eee;
+}
+
+select, input {
+       font-size: 0.95em;
+       line-height: 1.2em;
+       background: #111111;
+       border: 1px solid #333333;
+}
+
+.main_container {
+       border: 1px solid #e2e2e2;
+       background: #0a0b0e;
+}
+
+div.moving_box {
+       background-color: #808080;
+       border: 1px solid #65869E;
+}
+
+li[dirtype=file] { background-image: url(images/audio_li.png); }
+li[dirtype=directory] { background-image: url(images/folder_li.png); }
+li[dirtype=artist] { background-image: url(images/media-artist_li.png); }
+li[dirtype=album] { background-image: url(images/media-album_li.png); }
+li[dirtype=playlist] { background-image: url(images/playlist_li.png); }
+
+/* used to select the filename */
+li[btype=search] > span {
+       color: #999;
+}
+
+
+div.slider_pointer {
+        border:                1px solid #333333;
+        background-color: #305050;
+}
+
+[slider_moving] {
+       cursor: move;
+}
+
+img.act_button {
+       height: 25px;
+       width: 25px;
+       opacity: 0.70;
+}
+img.act_button:hover {
+       opacity: 1.0;
+}
+
+hr.server_settings {
+       color: #333333;
+       background-color: #333333;
+}
+
+div.overlay, div.selection_menu, #status_bar, div.slider_main,
+div#pagination, div#pagination_options, input#quickadd
+{
+       border: 1px solid #333333;
+       background-color: #111111;
+}
+
+div.settings_container, div.settings_header, 
+div#qa_suggestions, div#sidebar_header,
+div#streaming_display, div.player_control,
+div.popup
+{
+       border: 1px solid #333333;
+       background-color: black; 
+}
+
+div.settings_header {
+       border-bottom: 0px;
+}
+
+input#quickadd {
+       font-size: 12px;
+       color: #999;
+}
+input#quickadd:hover, input#quickadd:focus {
+       background-color: black;
+       color: #eee;
+}
+
+div.big_albumart {
+       background-color: black;
+}
+
+div#sidebar_header {
+       font-size: 0.8em;
+}
+
+div#sidebar_display {
+       border: 1px solid black;
+       background-color: #111111;
+       border: 1px solid #333333;
+       font-size: 0.992em;
+       line-height: 1.3em;
+}
+
+/* for browser action buttons */
+
+/* match add buttons */
+li.browser_button_add {
+       background: url(images/add_15.png) no-repeat top left;
+}
+
+/* current page on the pagination list */
+li[cpage] {
+       background-color: #800000;
+}
+
+span.plse {
+       background-image: url(images/audio_li.png);
+}
+
+span.plse:hover,
+ul.recommended > li > ul > li:hover ,
+[selected],
+li.menuitem:hover,
+#streaming_display li:hover,
+span.qa_element[qa_selected]
+{
+       background-color: #333333; 
+}
+
+li.playlist_popup, 
+span.playlist_popup, 
+ul#pagination_list > li,
+.browse_type,
+ul.browser_field,
+#playlist 
+{
+       border: 1px solid #333333;
+}
+
+li.menuitem {
+       border: 1px solid #555555;
+       border-top: none;
+       background: #111111;
+}
+
+tr.playlist {
+       border: 0px;
+}
+
+tr.moving {
+       border-top: 1px solid #b0bdec;
+       color: gray;
+}
+
diff --git a/theme/dark/theme.js b/theme/dark/theme.js
new file mode 100644 (file)
index 0000000..1738b95
--- /dev/null
@@ -0,0 +1,40 @@
+
+/* number of "windows" on the directory browser */
+var BROWSER_NUM = 3;
+
+/* the border around the playing item in the playlist */
+var PLAYLIST_PLAYING_STYLE = "1px solid #800000";
+
+/* this is the color thing that you add blink in */
+var DEFAULT_BLINK_COLOR = "#C1DAD6";
+
+var BASE_IMG_DIR = "../theme/dark/images/";
+
+var IMAGE = new Object();
+
+IMAGE.BUTTON_PLAY              = BASE_IMG_DIR + "play_25.png";
+IMAGE.BUTTON_STOP              = BASE_IMG_DIR + "stop_25.png";
+IMAGE.BUTTON_PAUSE             = BASE_IMG_DIR + "pause_25.png";
+IMAGE.BUTTON_PREVIOUS          = BASE_IMG_DIR + "previous_25.png";
+IMAGE.BUTTON_NEXT              = BASE_IMG_DIR + "next_25.png";
+
+IMAGE.BROWSER_PLAYLIST_REMOVE  = BASE_IMG_DIR + "disabled_15.png";
+
+IMAGE.SERVER_SETTINGS_ENABLED  = BASE_IMG_DIR + "enabled_15.png";
+IMAGE.SERVER_SETTINGS_DISABLED         = BASE_IMG_DIR + "disabled_15.png";
+IMAGE.SERVER_SETTINGS_XFADE_DOWN= BASE_IMG_DIR + "left_15.png";
+IMAGE.SERVER_SETTINGS_XFADE_UP = BASE_IMG_DIR + "right_15.png";
+
+IMAGE.CLOSE                    = BASE_IMG_DIR + "close_15.png";
+IMAGE.WORKING                  = BASE_IMG_DIR + "working.gif";
+
+IMAGE.MENU_ITEM_CROP           = BASE_IMG_DIR + "crop_30.png";
+IMAGE.MENU_ITEM_REMOVE         = BASE_IMG_DIR + "remove_30.png";
+IMAGE.MENU_ITEM_DIRECTORY      = BASE_IMG_DIR + "folder_30.png";
+
+IMAGE.PAGINATION_FOLLOW                = BASE_IMG_DIR + "follow-playing-yes.png";
+IMAGE.PAGINATION_NOFOLLOW      = BASE_IMG_DIR + "follow-playing-no.png";
+
+IMAGE.JUMP_CURRENT             = BASE_IMG_DIR + "jump-to-current.png";
+
+IMAGE.STREAM_BGCOLOR           = "1118481";
diff --git a/theme/default/images/add_15.png b/theme/default/images/add_15.png
new file mode 100644 (file)
index 0000000..d341334
Binary files /dev/null and b/theme/default/images/add_15.png differ
diff --git a/theme/default/images/audio_li.png b/theme/default/images/audio_li.png
new file mode 100644 (file)
index 0000000..e8ed100
Binary files /dev/null and b/theme/default/images/audio_li.png differ
diff --git a/theme/default/images/close.png b/theme/default/images/close.png
new file mode 100644 (file)
index 0000000..c983059
Binary files /dev/null and b/theme/default/images/close.png differ
diff --git a/theme/default/images/close_15.png b/theme/default/images/close_15.png
new file mode 100644 (file)
index 0000000..be5676c
Binary files /dev/null and b/theme/default/images/close_15.png differ
diff --git a/theme/default/images/crop_30.png b/theme/default/images/crop_30.png
new file mode 100644 (file)
index 0000000..ad81d80
Binary files /dev/null and b/theme/default/images/crop_30.png differ
diff --git a/theme/default/images/disabled_15.png b/theme/default/images/disabled_15.png
new file mode 100644 (file)
index 0000000..e5f1919
Binary files /dev/null and b/theme/default/images/disabled_15.png differ
diff --git a/theme/default/images/enabled_15.png b/theme/default/images/enabled_15.png
new file mode 100644 (file)
index 0000000..63adab8
Binary files /dev/null and b/theme/default/images/enabled_15.png differ
diff --git a/theme/default/images/folder_30.png b/theme/default/images/folder_30.png
new file mode 100644 (file)
index 0000000..7bdcd08
Binary files /dev/null and b/theme/default/images/folder_30.png differ
diff --git a/theme/default/images/folder_li.png b/theme/default/images/folder_li.png
new file mode 100644 (file)
index 0000000..a4d80fa
Binary files /dev/null and b/theme/default/images/folder_li.png differ
diff --git a/theme/default/images/follow-playing-no.png b/theme/default/images/follow-playing-no.png
new file mode 100644 (file)
index 0000000..3597926
Binary files /dev/null and b/theme/default/images/follow-playing-no.png differ
diff --git a/theme/default/images/follow-playing-yes.png b/theme/default/images/follow-playing-yes.png
new file mode 100644 (file)
index 0000000..5ec364d
Binary files /dev/null and b/theme/default/images/follow-playing-yes.png differ
diff --git a/theme/default/images/jump-to-current.png b/theme/default/images/jump-to-current.png
new file mode 100644 (file)
index 0000000..499ecae
Binary files /dev/null and b/theme/default/images/jump-to-current.png differ
diff --git a/theme/default/images/left_15.png b/theme/default/images/left_15.png
new file mode 100644 (file)
index 0000000..525df34
Binary files /dev/null and b/theme/default/images/left_15.png differ
diff --git a/theme/default/images/media-album_li.png b/theme/default/images/media-album_li.png
new file mode 100644 (file)
index 0000000..a27323c
Binary files /dev/null and b/theme/default/images/media-album_li.png differ
diff --git a/theme/default/images/media-artist_li.png b/theme/default/images/media-artist_li.png
new file mode 100644 (file)
index 0000000..c87c715
Binary files /dev/null and b/theme/default/images/media-artist_li.png differ
diff --git a/theme/default/images/next_25.png b/theme/default/images/next_25.png
new file mode 100644 (file)
index 0000000..c026270
Binary files /dev/null and b/theme/default/images/next_25.png differ
diff --git a/theme/default/images/pause_25.png b/theme/default/images/pause_25.png
new file mode 100644 (file)
index 0000000..29650ef
Binary files /dev/null and b/theme/default/images/pause_25.png differ
diff --git a/theme/default/images/play_25.png b/theme/default/images/play_25.png
new file mode 100644 (file)
index 0000000..a6af04c
Binary files /dev/null and b/theme/default/images/play_25.png differ
diff --git a/theme/default/images/playlist_li.png b/theme/default/images/playlist_li.png
new file mode 100644 (file)
index 0000000..627a858
Binary files /dev/null and b/theme/default/images/playlist_li.png differ
diff --git a/theme/default/images/previous_25.png b/theme/default/images/previous_25.png
new file mode 100644 (file)
index 0000000..8f3fcf9
Binary files /dev/null and b/theme/default/images/previous_25.png differ
diff --git a/theme/default/images/remove_30.png b/theme/default/images/remove_30.png
new file mode 100644 (file)
index 0000000..3a84844
Binary files /dev/null and b/theme/default/images/remove_30.png differ
diff --git a/theme/default/images/right_15.png b/theme/default/images/right_15.png
new file mode 100644 (file)
index 0000000..0cc81ad
Binary files /dev/null and b/theme/default/images/right_15.png differ
diff --git a/theme/default/images/stop_25.png b/theme/default/images/stop_25.png
new file mode 100644 (file)
index 0000000..a31a1e6
Binary files /dev/null and b/theme/default/images/stop_25.png differ
diff --git a/theme/default/images/working.gif b/theme/default/images/working.gif
new file mode 100644 (file)
index 0000000..42ff5c6
Binary files /dev/null and b/theme/default/images/working.gif differ
diff --git a/theme/default/theme.css b/theme/default/theme.css
new file mode 100644 (file)
index 0000000..0e78944
--- /dev/null
@@ -0,0 +1,189 @@
+/* top right bottom left */
+body {
+       font-size: 12.4px;
+       font-family: sans-serif;
+       background-color: #f7f7f7; 
+}
+
+a {
+       color: black;
+}
+
+select, input  {
+       font-size: 0.95em;
+       line-height: 1.2em;
+       border: 1px solid #BFCFCC;
+}
+
+hr.server_settings {
+       color: #9db2b1;
+       background-color: #9db2b1;
+}
+
+.main_container, #playlist {
+       border: 1px solid #e2e2e2;
+       background: #f1f1f1;
+}
+
+#playlist {
+       cursor: default;
+}
+
+tr.playlist {
+       border: 0px;
+}
+
+div.moving_box {
+       background-color: #f1f1f1;
+       border: 1px solid #65869E;
+}
+
+tr.moving {
+       border-top: 1px solid #b0bdec;
+       color: gray;
+}
+
+li[dirtype=file] { background-image: url(images/audio_li.png); }
+li[dirtype=directory] { background-image: url(images/folder_li.png); }
+li[dirtype=artist] { background-image: url(images/media-artist_li.png); }
+li[dirtype=album] { background-image: url(images/media-album_li.png); }
+li[dirtype=playlist] { background-image: url(images/playlist_li.png); }
+
+/* used to signify that this is a search result and is used in std/browser.js */
+li[btype=search] { }
+
+/* used to select the filename */
+li[btype=search] > span {
+       color: gray;
+}
+
+div.slider_main, div.popup {
+       border: 1px solid #B0BDEC; 
+       background-color: #DEE7F7; 
+}
+
+div.slider_pointer {
+        border:                1px solid #a20000;
+        background-color: #ffcccc;
+}
+
+[slider_moving] {
+       cursor: move;
+}
+
+img.act_button {
+       height: 25px;
+       width: 25px;
+       opacity: 0.70;
+}
+img.act_button:hover {
+       opacity: 1.0;
+}
+
+#status_bar {
+       border: 1px solid #BDD8DA;
+       background: #DFEFF0;
+}
+
+div.settings_container, div.settings_header,
+div.player_control, div#streaming_display {
+       border: 1px solid #9db2b1;
+       background: #e7e7e7;
+}
+div.settings_header {
+       border-bottom: 0px;
+}
+
+input#quickadd {
+       font-size: 12px;
+       color: gray;
+       background: #f1f1f1;
+}
+
+input#quickadd:hover, input#quickadd:focus {
+       background: white;
+       border: 1px solid #9db2b1;
+}
+input#quickadd:focus {
+       color: black;
+}
+
+div#qa_suggestions {
+       background: white; 
+       border: 1px solid #ccc; 
+}
+
+span.qa_element[qa_selected] {
+       background-color: #A5F2F3;
+}
+
+div.big_albumart {
+       background-color: black;
+}
+
+p#albumart_txt {
+       color: white;
+}
+
+div#sidebar_header {
+       background: #f1f1f1;
+       border: 1px solid #9db2b1;
+       font-size: 0.9em;
+}
+
+div#sidebar_display {
+       border: 1px solid black;
+       background-color: #ffffff;
+       border: 1px solid #9db2b1;
+       font-size: 0.992em;
+       line-height: 1.3em;
+}
+
+
+/* for browser action buttons */
+
+/* match add buttons */
+li.browser_button_add {
+       background: url(images/add_15.png) no-repeat top left;
+}
+
+li.playlist_popup,
+span.playlist_popup {
+       border: 1px solid #B0BDEC;
+}
+
+div#pagination, div#pagination_options,
+div.overlay, div.selection_menu {
+       border: 1px solid #bfcfcc;
+       background: #f1f1f1;
+}
+
+ul#pagination_list > li,
+ul.browser_field,
+.browse_type {
+       border: 1px solid #bfcfcc;
+}
+
+/* current page on the pagination list */
+li[cpage] {
+       background-color: green;
+}
+
+span.plse {
+       background-image: url(images/audio_li.png);
+}
+
+span.plse:hover,
+ul.recommended > li > ul > li:hover ,
+[selected],
+li.menuitem:hover,
+#streaming_display li:hover
+{
+       background-color: #DEE7F7; 
+}
+
+li.menuitem {
+       border: 1px solid #9DB2B1;
+       border-top: none;
+       background-color: #f1f1f1;
+}
diff --git a/theme/default/theme.js b/theme/default/theme.js
new file mode 100644 (file)
index 0000000..ae998e4
--- /dev/null
@@ -0,0 +1,42 @@
+
+/* number of "windows" on the directory browser */
+var BROWSER_NUM = 3;
+
+/* the border around the playing item in the playlist */
+var PLAYLIST_PLAYING_STYLE = "1px solid #a20000";
+
+/* this is the color thing that you add blink in */
+var DEFAULT_BLINK_COLOR = "#C1DAD6";
+
+var BASE_IMG_DIR = "../theme/default/images/";
+
+var IMAGE = new Object();
+
+IMAGE.BUTTON_PLAY              = BASE_IMG_DIR + "play_25.png";
+IMAGE.BUTTON_STOP              = BASE_IMG_DIR + "stop_25.png";
+IMAGE.BUTTON_PAUSE             = BASE_IMG_DIR + "pause_25.png";
+IMAGE.BUTTON_PREVIOUS          = BASE_IMG_DIR + "previous_25.png";
+IMAGE.BUTTON_NEXT              = BASE_IMG_DIR + "next_25.png";
+
+IMAGE.BROWSER_PLAYLIST_REMOVE  = BASE_IMG_DIR + "disabled_15.png";
+
+IMAGE.SERVER_SETTINGS_ENABLED  = BASE_IMG_DIR + "enabled_15.png";
+IMAGE.SERVER_SETTINGS_DISABLED         = BASE_IMG_DIR + "disabled_15.png";
+IMAGE.SERVER_SETTINGS_XFADE_DOWN= BASE_IMG_DIR + "left_15.png";
+IMAGE.SERVER_SETTINGS_XFADE_UP = BASE_IMG_DIR + "right_15.png";
+
+IMAGE.CLOSE                    = BASE_IMG_DIR + "close_15.png";
+IMAGE.WORKING                  = BASE_IMG_DIR + "working.gif";
+
+IMAGE.MENU_ITEM_CROP           = BASE_IMG_DIR + "crop_30.png";
+IMAGE.MENU_ITEM_REMOVE         = BASE_IMG_DIR + "remove_30.png";
+IMAGE.MENU_ITEM_DIRECTORY      = BASE_IMG_DIR + "folder_30.png";
+
+IMAGE.PAGINATION_FOLLOW                = BASE_IMG_DIR + "follow-playing-yes.png";
+IMAGE.PAGINATION_NOFOLLOW      = BASE_IMG_DIR + "follow-playing-no.png";
+
+IMAGE.JUMP_CURRENT             = BASE_IMG_DIR + "jump-to-current.png";
+
+
+/* this should be whatever the backgroun is converted from hex to dec */
+IMAGE.STREAM_BGCOLOR           = "15198183";
diff --git a/theme/thexy/images/activity.gif b/theme/thexy/images/activity.gif
new file mode 100644 (file)
index 0000000..915c198
Binary files /dev/null and b/theme/thexy/images/activity.gif differ
diff --git a/theme/thexy/images/add.png b/theme/thexy/images/add.png
new file mode 100644 (file)
index 0000000..5b051f6
Binary files /dev/null and b/theme/thexy/images/add.png differ
diff --git a/theme/thexy/images/album.png b/theme/thexy/images/album.png
new file mode 100644 (file)
index 0000000..91f7aff
Binary files /dev/null and b/theme/thexy/images/album.png differ
diff --git a/theme/thexy/images/artist.png b/theme/thexy/images/artist.png
new file mode 100644 (file)
index 0000000..f6041b7
Binary files /dev/null and b/theme/thexy/images/artist.png differ
diff --git a/theme/thexy/images/back.png b/theme/thexy/images/back.png
new file mode 100644 (file)
index 0000000..47ba205
Binary files /dev/null and b/theme/thexy/images/back.png differ
diff --git a/theme/thexy/images/bkg.jpg b/theme/thexy/images/bkg.jpg
new file mode 100644 (file)
index 0000000..805a292
Binary files /dev/null and b/theme/thexy/images/bkg.jpg differ
diff --git a/theme/thexy/images/browse_bkg.png b/theme/thexy/images/browse_bkg.png
new file mode 100644 (file)
index 0000000..7074b12
Binary files /dev/null and b/theme/thexy/images/browse_bkg.png differ
diff --git a/theme/thexy/images/browse_media.png b/theme/thexy/images/browse_media.png
new file mode 100644 (file)
index 0000000..8f9d710
Binary files /dev/null and b/theme/thexy/images/browse_media.png differ
diff --git a/theme/thexy/images/content_bkg.jpg b/theme/thexy/images/content_bkg.jpg
new file mode 100644 (file)
index 0000000..e93e177
Binary files /dev/null and b/theme/thexy/images/content_bkg.jpg differ
diff --git a/theme/thexy/images/control_bkg.jpg b/theme/thexy/images/control_bkg.jpg
new file mode 100644 (file)
index 0000000..34b5e87
Binary files /dev/null and b/theme/thexy/images/control_bkg.jpg differ
diff --git a/theme/thexy/images/crop_selection.png b/theme/thexy/images/crop_selection.png
new file mode 100644 (file)
index 0000000..d1ecee3
Binary files /dev/null and b/theme/thexy/images/crop_selection.png differ
diff --git a/theme/thexy/images/default_album_art.jpg b/theme/thexy/images/default_album_art.jpg
new file mode 100644 (file)
index 0000000..96d9759
Binary files /dev/null and b/theme/thexy/images/default_album_art.jpg differ
diff --git a/theme/thexy/images/directory.png b/theme/thexy/images/directory.png
new file mode 100644 (file)
index 0000000..32a6c51
Binary files /dev/null and b/theme/thexy/images/directory.png differ
diff --git a/theme/thexy/images/disabled.png b/theme/thexy/images/disabled.png
new file mode 100644 (file)
index 0000000..561460d
Binary files /dev/null and b/theme/thexy/images/disabled.png differ
diff --git a/theme/thexy/images/enabled.png b/theme/thexy/images/enabled.png
new file mode 100644 (file)
index 0000000..2f86f0a
Binary files /dev/null and b/theme/thexy/images/enabled.png differ
diff --git a/theme/thexy/images/file.png b/theme/thexy/images/file.png
new file mode 100644 (file)
index 0000000..881f1b9
Binary files /dev/null and b/theme/thexy/images/file.png differ
diff --git a/theme/thexy/images/fileclose.png b/theme/thexy/images/fileclose.png
new file mode 100644 (file)
index 0000000..95458e0
Binary files /dev/null and b/theme/thexy/images/fileclose.png differ
diff --git a/theme/thexy/images/forward.png b/theme/thexy/images/forward.png
new file mode 100644 (file)
index 0000000..966b073
Binary files /dev/null and b/theme/thexy/images/forward.png differ
diff --git a/theme/thexy/images/jump.png b/theme/thexy/images/jump.png
new file mode 100644 (file)
index 0000000..966b073
Binary files /dev/null and b/theme/thexy/images/jump.png differ
diff --git a/theme/thexy/images/lcd.jpg b/theme/thexy/images/lcd.jpg
new file mode 100644 (file)
index 0000000..f826650
Binary files /dev/null and b/theme/thexy/images/lcd.jpg differ
diff --git a/theme/thexy/images/next.jpg b/theme/thexy/images/next.jpg
new file mode 100644 (file)
index 0000000..cc03537
Binary files /dev/null and b/theme/thexy/images/next.jpg differ
diff --git a/theme/thexy/images/next_light.jpg b/theme/thexy/images/next_light.jpg
new file mode 100644 (file)
index 0000000..3aac14e
Binary files /dev/null and b/theme/thexy/images/next_light.jpg differ
diff --git a/theme/thexy/images/pause.jpg b/theme/thexy/images/pause.jpg
new file mode 100644 (file)
index 0000000..dd62087
Binary files /dev/null and b/theme/thexy/images/pause.jpg differ
diff --git a/theme/thexy/images/pause_light.jpg b/theme/thexy/images/pause_light.jpg
new file mode 100644 (file)
index 0000000..caac291
Binary files /dev/null and b/theme/thexy/images/pause_light.jpg differ
diff --git a/theme/thexy/images/play.jpg b/theme/thexy/images/play.jpg
new file mode 100644 (file)
index 0000000..03b9555
Binary files /dev/null and b/theme/thexy/images/play.jpg differ
diff --git a/theme/thexy/images/play_light.jpg b/theme/thexy/images/play_light.jpg
new file mode 100644 (file)
index 0000000..9afc7b2
Binary files /dev/null and b/theme/thexy/images/play_light.jpg differ
diff --git a/theme/thexy/images/playlist.png b/theme/thexy/images/playlist.png
new file mode 100644 (file)
index 0000000..52ac9fa
Binary files /dev/null and b/theme/thexy/images/playlist.png differ
diff --git a/theme/thexy/images/previous.jpg b/theme/thexy/images/previous.jpg
new file mode 100644 (file)
index 0000000..0929b02
Binary files /dev/null and b/theme/thexy/images/previous.jpg differ
diff --git a/theme/thexy/images/previous_light.jpg b/theme/thexy/images/previous_light.jpg
new file mode 100644 (file)
index 0000000..c99dbdd
Binary files /dev/null and b/theme/thexy/images/previous_light.jpg differ
diff --git a/theme/thexy/images/remove_selection.png b/theme/thexy/images/remove_selection.png
new file mode 100644 (file)
index 0000000..85982b7
Binary files /dev/null and b/theme/thexy/images/remove_selection.png differ
diff --git a/theme/thexy/images/shadowAlpha.png b/theme/thexy/images/shadowAlpha.png
new file mode 100644 (file)
index 0000000..a2561df
Binary files /dev/null and b/theme/thexy/images/shadowAlpha.png differ
diff --git a/theme/thexy/images/slider.png b/theme/thexy/images/slider.png
new file mode 100644 (file)
index 0000000..22f854a
Binary files /dev/null and b/theme/thexy/images/slider.png differ
diff --git a/theme/thexy/images/stop.jpg b/theme/thexy/images/stop.jpg
new file mode 100644 (file)
index 0000000..fc269b1
Binary files /dev/null and b/theme/thexy/images/stop.jpg differ
diff --git a/theme/thexy/images/stop_light.jpg b/theme/thexy/images/stop_light.jpg
new file mode 100644 (file)
index 0000000..c70e5e3
Binary files /dev/null and b/theme/thexy/images/stop_light.jpg differ
diff --git a/theme/thexy/images/tools_bkg.jpg b/theme/thexy/images/tools_bkg.jpg
new file mode 100644 (file)
index 0000000..dcdb2d5
Binary files /dev/null and b/theme/thexy/images/tools_bkg.jpg differ
diff --git a/theme/thexy/theme.css b/theme/thexy/theme.css
new file mode 100644 (file)
index 0000000..2c5e440
--- /dev/null
@@ -0,0 +1,405 @@
+/*
+       Site: Thexy Theme v1.0
+       Date: Jan 12, 2008
+       Author: Gianni Chiappetta
+       Website: www.runlevel6.org
+       E-Mail: gianni@runlevel6.org
+       Description: base style sheet
+       Author Notes:   
+       
+*/
+
+/************/
+/* ELEMENTS */
+/************/
+
+body {
+       color: #000;
+       background: #e8e8e8 url(images/bkg.jpg) left top repeat-x fixed;
+       font-family: "Bitstream Vera Sans","Verdana";
+       text-decoration: none;
+       word-spacing: Normal;
+       text-align: left;
+       letter-spacing: 0;
+       text-indent: 0px;
+       line-height: 14px;
+       font-size: 13px;
+}
+
+a:link { text-decoration: none; color: #000; }
+a:visited { text-decoration: none; color: #000; }
+a:hover { text-decoration: none; color: #7d7d7d; }
+a:active { text-decoration: none; color: #000; }
+
+a img { border: 0; }
+
+hr { padding: 0 !important; }
+
+/*------------------------- Player Control -------------------------*/
+#player_control {
+       position: fixed;
+       background: #e8e8e8 url(images/bkg.jpg) left top repeat-x fixed;
+       top: 0px;
+       left: 0px;
+       margin: 0;
+       padding: 0 0 0 0;
+       height: 125px;
+       width: 100%;
+       overflow: visible;
+}
+
+/* Status Bar */
+#status_bar {
+       top: 95px;
+       left: 155px;
+       display: block;
+       height: auto;
+       padding: 0;
+       width: 372px;
+       text-align: left;
+       font-size: 10px;
+}
+
+/* Album Art */
+#player_control #albumart {
+       margin: 25px 25px 25px 0;
+       padding: 0;
+       float: right;
+}
+
+#player_control .thumbnailart {
+       margin: 0;
+       padding: 0;
+       border: 0;
+}
+
+#albumart_show p.fakelink {
+       display: block !important;
+       height: 16px !important;
+       width: 16px !important;
+       text-indent: -9999px;
+       background: transparent url(images/fileclose.png) top left no-repeat;
+}
+
+/* LCD */
+#player_control div.pc_artist {
+       top: 0px;
+       left: 150px;
+       margin: 0;
+       padding: 20px 10px;
+       width: 382px;
+       height: 105px;
+       background: transparent url(images/lcd.jpg) top left no-repeat;
+}
+
+#player_control div.pc_artist p {
+       margin: 0;
+       padding: 0 0 10px 0;
+}
+
+/* Controls */
+#player_control div.pc_ci {
+       top: 20px;
+       left: 14px;
+       width: 122px;
+       height: 125px;
+       /*background: transparent url(images/control_bkg.jpg) top left no-repeat;*/
+       overflow: visible;
+}
+
+#player_control div.pc_ci > div.nomargin {
+       position: fixed !important;
+}
+
+#player_control div.pc_ci .act_button {
+       margin: 0 !important;
+       padding: 0 !important;
+       height: 32px !important;
+       float: left;
+}
+
+#player_control div.pc_ci #previous_button, #player_control div.pc_ci #stop_button, #player_control div.pc_ci #next_button {
+       width: 31px !important;
+}
+
+#player_control div.pc_ci #pp_button {
+       width: 29px !important;
+}
+
+#player_control div.pc_ci #previous_button:hover, #player_control div.pc_ci #stop_button:hover, #player_control div.pc_ci #pp_button:hover, #player_control div.pc_ci #next_button:hover { opacity: 0.7; }
+
+/* Bitrate */
+#player_control div.pc_ci #disp_info {
+       display: block;
+       margin: 65px 0 0 0;
+       padding: 10px;
+       width: 520px;
+       text-align: right;
+       font-size: 10px;
+}
+
+/* Quick Add */
+#player_control div.pc_ci #quickadd {
+       left: 15px;
+       width: 110px;
+       height: 15px;
+       border: 1px solid #707070;
+       padding: 5px;
+       margin: -80px 0 0 0 !important;
+       -moz-border-radius: 4px;
+       -khtml-border-radius: 4px;
+       line-height: 14px;
+       font-size: 13px;
+       vertical-align: middle;
+}
+
+#player_control div.pc_ci #qa_suggestions {
+       margin: -34px 0 0 0;
+       padding: 5px 0;
+       border: 1px solid #707070;
+       background-color: #FFF;
+       -moz-border-radius-bottomleft: 4px;
+       -moz-border-radius-bottomright: 4px;
+       -moz-border-radius-topright: 4px;
+       -khtml-border-radius-bottomleft: 4px;
+       -khtml-border-radius-bottomright: 4px;
+       -khtml-border-radius-topright: 4px;
+       line-height: 14px;
+       font-size: 13px;
+}
+
+span.qa_element { padding: 5px; }
+span.qa_element[qa_selected] {  background-color: #e8e8e8; }
+
+/* Sliders */
+#player_control div.pc_sliders {
+       top: 0px;
+       left: 572px;
+}
+
+#player_control div.pc_sliders > div {
+       margin-top: 15px;
+}
+
+#player_control div.pc_sliders .slider_txt {
+       padding: 5px 0 10px 0;
+}
+
+#player_control div.pc_sliders .slider_main {
+       height: 10px !important;
+       background-color: #666;
+       -moz-border-radius: 5px;
+       -khtml-border-radius: 5px;
+}
+
+div.slider_pointer {
+       width: 15px;
+       height: 10px;
+       background: transparent url(images/slider.png) top left no-repeat;
+       cursor: pointer;
+}
+
+[slider_moving] {
+       cursor: move;
+}
+
+/* Media */
+div.selection_menu {
+       top: 140px;
+       left: 0px;
+       margin: 0;
+       padding: 0;
+       width: 150px;
+}
+
+#player_control div.selection_menu .menu_button, img.menu_button {
+       display: block !important;
+       margin: 0 auto 5px auto;
+       width: 128px !important;
+       height: 34px !important;
+}
+
+/* Tools */
+#player_control div.pc_other {
+       top: 265px;
+       left: 0px;
+       margin: 0;
+       width: 150px;
+       padding: 30px 0 0 20px;
+       background: transparent url(images/tools_bkg.jpg) top left no-repeat;
+}
+
+#player_control div.pc_other li, li.menuitem {
+       padding: 3px 0;
+       list-style-type: none;
+       line-height: 14px;
+       font-size: 13px;
+}
+
+div.popup {
+       margin: 0 0 0 50px !important;
+       padding: 5px;
+       background-color: #e8e8e8;
+       border: 1px solid #707070;
+       width: 300px;
+}
+
+div.popup:hover { color: #000; }
+
+/* Settings */
+#player_control div.pc_settings {
+       top: 280px;
+       left: 0px;
+       margin: 0 !important;
+       padding: 0 !important;
+       width: 150px;
+}
+
+#settings_header p {
+       padding: 0 0 0 20px !important;
+       margin: 0 !important;
+}
+#settings_header p:hover { color: #7d7d7d; }
+
+#settings_container {
+       background-color: #e8e8e8;
+       margin: 0 !important;
+       padding: 0 !important;
+       overflow: visible !important;
+       width: 149px !important;
+}
+
+#settings_content {
+       background: transparent url(images/content_bkg.jpg) top left repeat-x !important;
+       width: 145px !important;
+       margin: -5px 0 0 0 !important;
+       padding: 0 0 5px 6px !important;
+       font-size: 13px !important;
+       line-height: 14px !important;
+}
+
+.server_settings {
+       padding: 3px 0;
+       cursor: pointer;
+}
+
+/* Streaming */
+#streaming_display {
+       margin: 15px 0 0 0 !important;
+       padding: 0 !important;
+       width: 100px !important;
+}
+
+#streaming_display li {
+       padding: 3px 0;
+       list-style-type: none;
+       line-height: 14px;
+       font-size: 13px;
+}
+
+/* Content */
+#content {
+       position: absolute !important;
+       top: 125px !important;
+       left: 151px !important;
+       margin: 0 !important;
+       padding: 15px 0 0 0 !important;
+       /*background: transparent url(images/content_bkg.jpg) top left repeat-x fixed !important;*/
+       border-left: 1px solid #acacac !important;
+}
+
+tr[playing] { color: #FFF; background-color: #66BBCC; border-top: 1px solid #33BBCC; border-bottom: 1px solid #33BBCC; }
+ul.browser_field li[selected], tr[selected] { color: #FFF; background-color: #BBB; }
+
+/* Browser */
+.overlay {
+       top: 125px !important;
+       left: 151px !important;
+       margin: 0 !important;
+       padding: 15px 0 0 0 !important;
+       position: fixed;
+       background: transparent url(images/browse_bkg.png) top left repeat-x !important;
+       border-right: 1px solid #acacac !important;
+       border-left: 1px solid #acacac !important;
+}
+
+p.browse_type, input.browse_type, select.browse_type {
+       border: 1px outset #666;
+       cursor: pointer;
+       background-color: transparent;
+}
+input.browse_type { cursor: text; }
+
+span.plse { padding: 2px 0 2px 18px; }
+ul.browser_field li { background: transparent left middle no-repeat !important; padding: 3px 0 3px 18px !important; cursor: pointer; }
+span.plse[dirtype=file], li[dirtype=file] { background-image: url(images/file.png); }
+span.plse[dirtype=directory], li[dirtype=directory] { background-image: url(images/directory.png); }
+span.plse[dirtype=artist], li[dirtype=artist] { background-image: url(images/artist.png); }
+span.plse[dirtype=album], li[dirtype=album] { background-image: url(images/album.png); }
+span.plse[dirtype=playlist], li[dirtype=playlist] { background-image: url(images/playlist.png); }
+li[dirtype=parent] { background-image: url(images/back.png); }
+li.browser_button_add { padding-left: 18px !important; background: transparent url(images/add.png) no-repeat !important; cursor: pointer !important; }
+
+/* Sidebar */
+#sidebar_header, #sidebar_display {
+       position: fixed !important;
+       right: 0 !important;
+       margin: 0 !important;
+       border-left: 1px solid #acacac !important;
+}
+
+#sidebar_header {
+       top: 125px !important;
+       padding-top: 15px !important;
+       font-size: 10px;
+       font-weight: bold;
+       background: transparent url(images/content_bkg.jpg) top left repeat-x !important;
+       z-index: 10 !important;
+       overflow: visible;
+}
+
+#sidebar_header span[class=fakelink], #metadata_close {
+       display: block;
+       margin-top: -16px;
+       padding: 0 3px;
+       text-indent: -9999px;
+       width: 16px;
+       height: 16px;
+       background: transparent url(images/fileclose.png) top left no-repeat;
+       float: right;
+}
+
+#metadata_close { float: left !important; margin: 0 !important; }
+
+#metadata_open_lyrics, #metadata_open_description, #metadata_open_review {
+       display: block;
+       float: left !important;
+       margin: 0 !important;
+       padding: 0 3px;
+       text-indent: 0px !important;
+       width: auto !important;
+       background-image: none !important;
+}
+
+#sidebar_display {
+       top: 160px !important;
+       width: 270px;
+       max-width: 270px;
+       overflow: scroll;
+       font-size: small;
+       background-color: #e8e8e8 !important;
+       z-index: 9 !important;
+}
+
+#sidebar_display #sidebar_display_txt {
+       padding-left: 20px;
+}
+
+#sidebar_display ul {
+       list-style-position: inside;
+}
+
+/*------------------------- Misc -------------------------*/
+.nomargin { margin: 0 !important; padding: 0 !important; }
+.fakelink { cursor: pointer; color: #000; }
+.fakelink:hover { color: #7d7d7d; } 
diff --git a/theme/thexy/theme.js b/theme/thexy/theme.js
new file mode 100644 (file)
index 0000000..04e39f5
--- /dev/null
@@ -0,0 +1,40 @@
+
+/* number of "windows" on the directory browser */
+var BROWSER_NUM = 3;
+
+/* the border around the playing item in the playlist */
+var PLAYLIST_PLAYING_STYLE = "1px solid #33BBCC";
+
+/* this is the color thing that you add blink in */
+var DEFAULT_BLINK_COLOR = "#C1DAD6";
+
+var BASE_IMG_DIR = "../theme/thexy/images/";
+
+var IMAGE = new Object();
+
+IMAGE.BUTTON_PLAY              = BASE_IMG_DIR + "play_light.jpg";
+IMAGE.BUTTON_STOP     = BASE_IMG_DIR + "stop_light.jpg";
+IMAGE.BUTTON_PAUSE    = BASE_IMG_DIR + "pause_light.jpg";
+IMAGE.BUTTON_PREVIOUS = BASE_IMG_DIR + "previous_light.jpg";
+IMAGE.BUTTON_NEXT     = BASE_IMG_DIR + "next_light.jpg";
+
+IMAGE.BROWSER_PLAYLIST_REMOVE = BASE_IMG_DIR + "disabled.png";
+
+IMAGE.SERVER_SETTINGS_ENABLED   = BASE_IMG_DIR + "enabled.png";
+IMAGE.SERVER_SETTINGS_DISABLED  = BASE_IMG_DIR + "disabled.png";
+IMAGE.SERVER_SETTINGS_XFADE_DOWN= BASE_IMG_DIR + "back.png";
+IMAGE.SERVER_SETTINGS_XFADE_UP  = BASE_IMG_DIR + "forward.png";
+
+IMAGE.CLOSE    = BASE_IMG_DIR + "fileclose.png";
+IMAGE.WORKING  = BASE_IMG_DIR + "activity.gif";
+
+IMAGE.MENU_ITEM_CROP      = BASE_IMG_DIR + "crop_selection.png";
+IMAGE.MENU_ITEM_REMOVE    = BASE_IMG_DIR + "remove_selection.png";
+IMAGE.MENU_ITEM_DIRECTORY = BASE_IMG_DIR + "browse_media.png";
+
+IMAGE.PAGINATION_FOLLOW   = BASE_IMG_DIR + "enabled.png";
+IMAGE.PAGINATION_NOFOLLOW = BASE_IMG_DIR + "disabled.png";
+
+IMAGE.JUMP_CURRENT = BASE_IMG_DIR + "jump.png";
+
+IMAGE.STREAM_BGCOLOR = "1118481";
This page took 1.352296 seconds and 4 git commands to generate.