--- /dev/null
+Order Deny,Allow
--- /dev/null
+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
--- /dev/null
+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/
--- /dev/null
+
+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!
--- /dev/null
+
+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
--- /dev/null
+Order Allow,Deny
--- /dev/null
+config.xml
+metadata
--- /dev/null
+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
+
+
--- /dev/null
+
+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.
+
+
--- /dev/null
+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>
--- /dev/null
+<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>
--- /dev/null
+<?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)
+ {
+
+ }
+ }
+
+}
+
+?>
--- /dev/null
+<?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
--- /dev/null
+<?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'];
+ }
+}
+?>
--- /dev/null
+<?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;
+ }
+}
+?>
--- /dev/null
+<?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);
+ }
+}
+?>
--- /dev/null
+<?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
--- /dev/null
+<?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;
+ }
+}
+?>
--- /dev/null
+<?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' from Net_MPD_Playlist to Net_MPD_Playback.
+- Changed MPD.php to provied an object uniting the sub-objects. $mpd->Playlist->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->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->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>
--- /dev/null
+<?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);
+ }
+}
+?>
--- /dev/null
+<?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();
+ }
+
+?>
--- /dev/null
+<?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();
+?>
--- /dev/null
+<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>
--- /dev/null
+/* -*-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();
+ }
+ }
+ }
+}
--- /dev/null
+/* -*-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");
+ }
+ */
+}
+
+
+
+
+
--- /dev/null
+/* 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);
+ }
+ }
+ }
+ */
+}
--- /dev/null
+/* -*-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);
+ }
+ */
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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; }
+}
--- /dev/null
+/* 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;
+};
--- /dev/null
+/* 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]));
+ }
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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
+}
--- /dev/null
+/* 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;
+ }
+}
--- /dev/null
+/* 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.");
+ }
+}
+
--- /dev/null
+/* 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];
+ }
+}
--- /dev/null
+/* 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));
+ }
+*/
+}
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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]);
+ }
+
+}
+
+
--- /dev/null
+/* 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;
+ }
+ }
+*/
+}
--- /dev/null
+/* 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);
+
+ }
+ }
+}
+
+
--- /dev/null
+/* 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
+}
--- /dev/null
+/* 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);
+ }
+}
--- /dev/null
+/* 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(){}
+}
--- /dev/null
+/* 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]);
+ }
+ */
+ }
+}
--- /dev/null
+/* 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
+}
--- /dev/null
+/* 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;
+ }
+ }
+}
--- /dev/null
+/* 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));
+ }
+}
--- /dev/null
+/* 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");
+ }
+*/
+}
+
+
+
+
+
--- /dev/null
+/* 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;}
+}
--- /dev/null
+/* 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);
+ }
+ }
+
+}
--- /dev/null
+/* 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);
+ }
+ }
+}
--- /dev/null
+/* 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]";
--- /dev/null
+<?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:'
+ );
+?>
--- /dev/null
+/* 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]";
--- /dev/null
+<?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:'
+ );
+?>
--- /dev/null
+/* 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]";
--- /dev/null
+<?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:'
+ );
+?>
--- /dev/null
+/* 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]";
--- /dev/null
+<?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:'
+ );
+?>
--- /dev/null
+<?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";
+
+ }
+
+?>
--- /dev/null
+<?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) {}
+ }
+?>
--- /dev/null
+<?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?" Hide nonstandard [~]":" 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> </td><td> </td></tr>
+<tr><td colspan="2"><?php echo m("Show these fields in the playlist:");?> </td></tr>
+<tr><td> </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();'> ";
+ echo m("Show nonstandard") . " [+]</td></tr>";
+ }
+ echo "<tr id='ple_$i' ";
+ if($i>=3)
+ echo "style='display: none; ' ";
+ echo "><td> </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> </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>
--- /dev/null
+<?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>
--- /dev/null
+<?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>
--- /dev/null
+<?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);
+ }
+}
+
+?>
--- /dev/null
+<?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;
+ }
+?>
--- /dev/null
+<?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";
+?>
--- /dev/null
+<?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;
+ }
+
+?>
--- /dev/null
+<?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";
+?>
--- /dev/null
+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;
+}
--- /dev/null
+/*
+ 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);
+ }
+}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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);
+}
+
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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(/<i>/gi, "<i>");
+ txt = txt.replace(/<\/i>/gi, "</i>");
+ txt = txt.replace(/\n/g, "<br/>\n");
+ txt = txt.replace(/<p>/gi, "<br/><br/>\n");
+ txt = txt.replace(/<\/p>/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(/&/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);
+}
--- /dev/null
+/*
+ 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();
+}
--- /dev/null
+/*
+ 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"));
+ }
+ }
+}
+
--- /dev/null
+/*
+ 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);
+ }
+ }
+}
--- /dev/null
+/*
+ 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();
+ }
+}
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+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/
--- /dev/null
+/* 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;
+}
+
--- /dev/null
+
+/* 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";
--- /dev/null
+/* 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;
+}
--- /dev/null
+
+/* 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";
--- /dev/null
+/*
+ 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; }
--- /dev/null
+
+/* 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";