# -*- Mode: Org -*-
* Previous Issues
** DONE Fix WAVE/MP3 formats to support MP3 audio in a WAVE container
   Instead of WaveAudio generating a nasty "compression not supported" error,
   its is_type() classmethod should return False.
   And, MP3Audio's is_type() classmethod should check for MP3 compressed
   RIFF WAVE containers.
   This isn't likely to mess up decoding, but may confuse ID3 tagging.
** DONE Allow file template to be specified on the command line
   When making new files with track2track, cd2track, etc.
** DONE Update the website with the latest documentation
** DONE Allow a unified %(album_track_number)s file template field
   If there's no album number, it's simply 2 digits of track number.
   Otherwise, it's a combination of the two fields.
   For example, album_number = 2 and track_number = 13 results in
   "213" for a value.
** DONE Update trackcat to take a cuesheet argument when outputting FLACs
   Thus, one can perform:
   trackcat --cuesheet=file.cue file1.wav file2.wav file3.wav -t flac -o cd.flac
   which will embed "file.cue" into "cd.flac" using metaflac.
   Though no other format I'm aware of supports this kind of cuesheet handling,
   being able to easily build solid disc images of a single FLAC file
   is much of the reason for trackcat/tracksplit.
** DONE Don't remove undone tracklint entries from its undo DB
   Since its checksum will change anyway and no longer match,
   explicitly removing the entry is no longer necessary
** DONE Support FLAC padding
   If changes to FLAC metadata are small enough, write over the
   padding (if present) rather than rewrite the whole file - like metaflac.
   This approach should speed up tagging considerably.
** DONE Fix image support in ID3v2
   Very large images can take a very long time to load.
** DONE Fix programs to key on album number and track number
   Certain programs, such as trackcmp, work on tracks across two directories
   and key on track number to determine which to compare to which.
   These need to be updated to use both track number and album number.
** DONE Adjust wave conversions to use album number, if present
   For example, converting track_number 15 and album_number 2 to WAVE
   should make a file "track215.cdda.wav" which then properly converts
   back to track_number 15 and album_number 2 if read.
** DONE Improve XMCD handling
   Support for XMCD files often breaks down if one or more tracks
   are missing.
   In some cases, there's no fix to be had (track2xmcd)
   but in most instances it should be made to work correctly.
** DONE Perform type inference wherever possible
   Anything with a single output file (trackcat and record2track)
   should be able to infer its output type from the track suffix, if possible.
** DONE Add "comment" field support to all metadata types
   Don't forget to add unit tests for comment field.
*** TODO Sort "comment" fields correctly across all metadata types
*** TODO Add --comment support to tracktag
** DONE Fix ID3v2 image support to handle Unicode descriptions
   The current implementation falls down on UTF-16 input,
   but I should have a solution from the COMM frame handler.
** DONE Limit ID3v2.2/2.3 to UCS-2 encoding
   The current implementation treats UCS-2 the same as UTF-16.
   This needs to be fixed so that really high unicode characters
   (above U+FFFF) are replaced with something within spec.
** DONE Unify ID3v2 frame handling
   In the beginning, there were text frames and Everything Else.
   Text frames were unicode strings,
   and Everything Else was a binary string of whatever.
   Now that ID3 is cluttered with APIC frames and COMM frames that need
   special treatment, ID3v2 needs an overhaul to more resemble FlacMetaData.
*** DONE Ensure unknown frames are displayed correctly
    Anything that's not text, images or comments should get some sort
    of proper display instead of a Python object string.
** DONE Add app testing to the unit test suite
   Though not everything is unit-testable
   (such as the CD handling programs or anything X11)
   a lot of the batch programs are to some degree:
- [X] coverdump
- [X] track2track
- [X] trackcat
- [X] trackcmp
- [X] tracklength
- [X] tracklint
- [X] trackrename
- [X] tracksplit
- [X] tracktag
** DONE Add verbosity levels to programs
   Every batch program should support a -V --verbose flag
   with options for "silence","normal" (the default) and "debug".
   Silence shuts off everything but error messages.
   Normal is standard output behavior.
   Debug for additional debugging output.
- [X] cd2track
- [X] cd2xmcd
- [X] coverdump
- [X] record2track
- [X] track2cd (this will need to forward verbosity to cdrecord)
- [X] track2track
- [X] track2xmcd
- [X] trackcmp
- [X] trackrename
- [X] tracksplit
- [X] tracktag
- [X] tracklint
** DONE Add compression percentage display to trackinfo
   Though not massively useful, it'd be neat to see just how
   compressed audio tracks are, as a percentage of their original size.
** DONE Add support for W??? frames to ID3v2
   The various W??? frames are really just URLs and don't need to
   be displayed as hex-encoded blobs.
** DONE Add CUE/TOC support to track2cd
   It should be possible to burn a selection of tracks, or a disc image,
   from a cuesheet with all its indexes/ISRC/catalog data intact
   by passing --cue to track2cd.
** DONE Unify CUE/TOC support
   Cuesheets and cdrdao TOC files are largely interchangeable.
   They both feature a listing of track offsets and, optionally,
   CD-TEXT data, ISRCs and so on.
   These formats should be unified such that any program will
   handle them both automatically.
- [X] tracksplit
- [X] trackcat
- [X] tracktag
*** DONE Update docs to mention CUE/TOC interchangeability
- [X] tracksplit
- [X] trackcat
- [X] tracktag
*** DONE Support cuesheet from FlacMetaData directly
    Since we're parsing CUE/TOC files anyway, this data can be used
    to build FLAC CUESHEET blocks directly instead of punting
    this task to metaflac.
*** DONE Add unit tests for TOC/CUE files, as well as embedded FLAC cuesheets
** DONE Update copyright text for 2009
** DONE Preserve metadata when using trackcat
   Any fields shared by all tracks should be merged into metadata
   for the newly combined track.
** DONE Don't route data though WAVE files unless necessary
   Currently, track2track routes through WAVE if both ends happen to
   support foreign RIFF chunks, whether the files have such chunks or not.
   This behavior needs to be modified such that only source files
   which actually have foreign chunks, and a target format that supports them,
   results in a pass through RIFF WAVE.
** DONE Convert editxmcd to PyGTK
   Although the dialog(1)-based version works in terminals and is curses-based,
   it's extremely hokey, error-prone and doesn't support any cut & paste.
   This needs to be reimplemented in PyGTK (since coverview already uses it)
   and made into a stable app someone would want to use.
*** DONE Update XMCD support
    The current handling of XMCD files treats them only as very primative
    AlbumMetaData implementations.  This must be updated into something
    round-trippable if editxmcd is to be modernized.
**** DONE Add XMCD unit tests
**** DONE Update XMCD API documentation
** DONE Require Python 2.5
   Since Python 2.4 is in bugfix-only mode and barely supported,
   it's best to move the minimum version to Python 2.5 or better
   (which has already been superceded by Python 2.6).
   This reduces the amount of Python versions to test on
   and allows the use of more modern Python features which
   makes the code less clunky.
*** TODO Update documentation to mention Python 2.5 requirement.
** DONE Expand WavPack's APEv2 tag coverage
   WavPack's official specification defines APEv2 tags such as
   "Cuesheet" and "Cover Art" which the APEv2 standard does not.
   It would be helpful to make WavPack's APEv2 tags a superset of regular APEv2.
*** DONE Add image support to WavePackAPEv2
*** DONE Add cuesheet support to WavePackAPEv2
** DONE Build unified cuesheet interface
   Once both FLAC and WavPack support embedded cuesheets,
   there will need to be a unified interface to support them.
   I expect this will be a simple pair of get_cuesheet/set_cuesheet
   methods, probably attached to the AudioFiles themselves
   rather than to MetaData objects.
*** DONE Alter FLAC-specific cuesheet documentation to be more general
*** DONE Ensure cuesheets are transferred properly when transcoding
*** DONE Update trackcat to use the interface
*** DONE Update tracksplit to use the interface
*** DONE Update track2cd to use the interface
*** DONE Document cuesheet interface
*** DONE Add unit tests for embedded cuesheets across all formats
*** DONE Add cuesheet import option to tracktag
    This can also use the --cue flag,
    for consistency with other image-handling programs like tracksplit.
    If given with a single, album-length track, --cue will import a cuesheet.
    If given with multiple tracks or a single track that's too short,
    --cue will function like --xmcd and act as a metadata source.
*** DONE Update track2xmcd to support getting an XMCD file from CD image
** DONE Convert to Muspack SV8
   Now that Musepack SV8 is finalized, it should be the new default.
   The old SV7 command-line tools aren't well supported and don't
   seem to work outside of x86 platforms.
   SV7 streams are, in theory, backwards compatible so switching
   shouldn't be a problem.
** DONE Update coverview to look more standard
   It's currently a haphazard assortment of widgets
   rather than anything like a proper GTK app.
   It should be tweaked to look better.
** DONE Improve transcoding robustness
   Just about all of the to_pcm() and from_pcm() methods expect
   that their subprocess calls will work as expected.
   Though rare in practice, these need to be checked in case
   the child processes fail for any reason.
*** DONE Check for invalid input/output files/permissions errors
    If an output file can't be read/written to for some reason
    (invalid permissions, etc.) generate a proper error message
    instead of throwing ugly IOExceptions or confusing errors.
- [X] cd2track
- [X] cd2xmcd
- [X] coverdump
- [X] editxmcd
- [X] record2track
- [X] track2cd
- [X] track2track
- [X] track2xmcd
- [X] trackcat
- [X] trackcmp
- [X] trackinfo
- [X] tracklength
- [X] tracklint
- [X] trackplay
- [X] trackrename
- [X] tracksplit
- [X] tracktag
** DONE Make text output consistent
   Currently, command-line programs generate output using a
   selection of scattered print statements - often accompanied by
   if blocks when verbosity is indicated - and haphazardly
   filtered through unicode.
   This should be replaced by a unified message system similar
   to Python's built-in logging module which can abstract away
   these difficulties.
*** DONE Convert tty output to gettext-based strings
    This will not only make output messages more consistent across the tools,
    but will also allow for foreign language translations in the future.
- [X] cd2track
- [X] cd2xmcd
- [X] coverdump
- [X] record2track
- [X] track2cd
- [X] track2track
- [X] track2xmcd
- [X] trackcat
- [X] trackcmp
- [X] trackinfo
- [X] tracklength
- [X] tracklint
- [X] trackplay
- [X] trackrename
- [X] tracksplit
- [X] tracktag
**** DONE Convert output from audiotools module to gettext-based strings
- [X] __aiff__.py
- [X] __ape__.py
- [X] __au__.py
- [X] cue.py
- [X] __flac__.py
- [X] __freedb__.py
- [X] __id3__.py
- [X] __id3v1__.py
- [X] __image__.py
- [X] __init__.py
- [X] __m4a__.py
- [X] __mp3__.py
- [X] __musepack__.py
- [X] __speex__.py
- [X] toc.py
- [X] __vorbiscomment__.py
- [X] __vorbis__.py
- [X] __wavpack__.py
- [X] __wav__.py
*** DONE Add unit tests for tty output
    All programs which generate output should be unit tested
    so that all code paths are assured of printing the messages they're
    supposed to print, at the streams they're supposed to print on,
    and in the proper encoding settings.
- [X] coverdump
- [X] track2track
- [X] track2xmcd
- [X] trackcat
- [X] trackcmp
- [X] trackinfo
- [X] tracklength
- [X] tracklint
- [X] trackrename
- [X] tracksplit
- [X] tracktag
*** DONE Convert --help output to gettext-based strings
*** DONE Convert GUI programs to gettext-based strings
- [X] coverview
- [X] editxmcd
*** DONE Convert "Usage" output to gettext-based strings
** DONE Update tracksplit's man page
   It now supports more of track2track's options
** DONE Support total tracks/total albums metadata fields
*** DONE Add support for fields in the metadata tags
- [X] Add support in Vorbis Comments
- [X] Add support in ID3v2
- [X] Add support in M4A
- [X] Add support in APEv2
*** DONE Add support in utilities
- [X] Add support in tracktag
- [X] Add support in cd2track
- [X] Add support in tracksplit
- [X] Add support in trackcat
*** DONE Add unit tests
*** DONE Add fields to --format output
*** DONE Update man pages with fields information
** DONE Integrate better MetaData merging
   There's a few areas in which MetaData from multiple sources must
   be merged in an intelligent manner,
   such as where tracksplit takes a source track an XMCD file.
   Now that a preliminary MetaData.merge() classmethod is in place,
   this process must be integrated consistently.
- [X] track2track
- [X] trackrename
- [X] tracksplit
- [X] tracktag
*** DONE Add unit tests for MetaData merging process
- [X] track2track
- [X] trackrename
- [X] tracksplit
- [X] tracktag
** DONE Improve M4A metadata handling
*** DONE Make M4A metadata updating less destructive
   Like FLAC, not all fields need to be wiped out when overwriting
   old metadata with new.
*** DONE Add more M4A-specific unit tests
** DONE Add more system information to audiotools-config
   All BIN-referenced binaries should be accounted for.
   Thumbnailing status and requirements should be shown.
** DONE Add cdinfo utility
   Analagous to trackinfo, but for an inserted CD.
   This would be a better location for cd2xmcd's "-i" option.
*** DONE Add cdinfo man page
*** DONE Link cdinfo man page to other utility man pages
** DONE Add manual page for audiotools.cfg
   It'll be easier to check what the options are from a man page
   rather than having to check the website or PDF doc.
** DONE Convert vorbiscomment dependency to Python
   This would remove the last app-based MetaData-setting utility
   and may pave the way for adding cover art to Ogg Vorbis
   (assuming I can find the standard for a secondary stream of image data)
** DONE Add metadata deletion capability
   It would be helpful to have the low-level capability of deleting
   either part of a MetaData tag or the entire tag altogether.
   For example, deleting the "track_name" field would delete
   a Vorbis comment's "TITLE" field.
   Or, deleting the MetaData from MP3 would remove all the ID3v2/ID3v1 tags.
*** DONE Add delattr to ID3v1
** DONE Integrate pyconstruct as a submodule
** DONE Add undo capability to editxmcd
** DONE Add --cue option to track2xmcd
   One should be able to pull metadata from CD images
   without having to embed the cuesheet.
*** DONE Add unit tests for track2xmcd's --cue option
*** DONE Update man page
** DONE Group --help output more intelligently
   For tools with a large number of options (such as track2track or tracktag)
   the --help output is particularly jumbled.
   Use more of optparse's features to make this output clearer.
- [X] cd2track
- [X] cd2xmcd
- [X] track2track
- [X] track2xmcd
- [X] tracksplit
- [X] tracktag
** DONE Check for FLAC metadata chunk overflow
   Although APEv2 and ID3 tags support very large objects (hundreds of MB),
   FLAC metadata chunks have a maxmimum of about 16MB per chunk,
   which may be hit accidentally.
** DONE Fix or replace Python's built-in aifc module
   The current implementation suffers from bugs.
*** DONE Document AIFF better
** DONE Add MusicBrainz support
   It would be helpful to have external metadata support beyond FreeDB,
   since FreeDB is very primitive.
*** DONE Ensure that MusicBrainz is interchangeable with FreeDB/XMCD
**** DONE Unify track2xmcd/track2mb, cd2xmcd/cd2mb
     Based on preliminary testing, MusicBrainz's output is better than FreeDB's
     but its album coverage is not as broad.
     In addition, nobody wants to run their albums through two separate tools
     in order to extract metadata for tagging.  The best solution is
     for tools to try both and output the one that's most complete.
**** DONE Extend editxmcd to MusicBrainz XML
     Although editxmcd was originally designed specifically for XMCD files
     and MusicBrainz's XML format differs radically, no one should have to
     know whether an album metadata file is one or the other.
     Therefore, editxmcd should be extended with additional fields
     to handle XML backend data if necessary.
**** DONE Handle multiple Release entries with single Disc ID
**** DONE Allow MusicBrainz XML output for new editxmcd files
     FreeDB output should also be an option, however.
*** DONE Add MusicBrainz protocol/format documentation
*** DONE Add MusicBrainz-specific unit tests
- [X] track2track
- [X] track2xmcd
- [X] trackrename
- [X] tracksplit
- [X] tracktag
*** DONE Update --help text to indicate MusicBrainz compatibility
- [X] cd2xmcd
- [X] editxmcd
- [X] track2track
- [X] track2xmcd
- [X] trackrename
- [X] tracksplit
- [X] tracktag
*** DONE Update man pages to indiciate MusicBrainz compatibility
- [X] cd2xmcd
- [X] editxmcd
- [X] track2track
- [X] track2xmcd
- [X] trackrename
- [X] tracksplit
- [X] tracktag
*** DONE Update documentation with MusicBrainz config file fields
*** DONE Ensure missing XML fields are handled correctly
    The MusicBrainz XML spec allows most fields to be missing altogether
    (such as <title>).  editxmcd should add these fields in the proper place
    if necessary.
**** DONE Add unit tests for improperly reordered XML fields
** DONE Ensure .glade files are found
   Not all systems place Python data files in the same locations.
** DONE Convert to_pcm()/from_pcm() to FrameList-based I/O
   Passing specifically-sized blobs of binary data between conversion
   routines worked well when those routines are little more than
   subprocess black-boxes.  However, this approach works less well
   whenever actual sample values are required, or when processing is needed.
   In those cases, going from integers to strings, converting the strings
   back to integers for processing, then bouncing them into strings once
   again becomes needless work.
   A more sensible approach is to keep all data as FrameList-compatible
   objects (stored as C-based lists of int32s behind-the-scenes)
   and convert that data to/from strings only at the beginning and end
   of processing.
*** DONE Build C-based audiotools.pcm.FrameList object
    This needs to closely match audiotools.FrameList's functionality
    and combine all the PCM conversion features from audiotools.pcmreader
**** DONE Integrate audiotools.pcm.FrameList with i_array structures
**** DONE Make audiotools.pcm.FrameList into a standalone object
     So standalone test codecs can use them, such as "flacenc"
**** DONE Unit test audiotools.pcm.FrameList
*** DONE Convert FLAC encoder/decoder to use FrameList objects
    - [X] flacenc
    - [X] audiotools.decoders.FlacDecoder
    - [X] audiotools.encoders.encode_flac
*** DONE Convert to_pcm()/from_pcm() routines to use FrameList objects
    - [X] AAC
    - [X] AIFF
    - [X] Sun AU
    - [X] FLAC
    - [X] M4A
    - [X] MP2
    - [X] MP3
    - [X] Ogg FLAC
    - [X] Ogg Speex
    - [X] WAVE
    - [X] WavPack
*** DONE Convert CDTrackReader/OffsetCDTrackReader to use FrameList objects
*** DONE Convert PCMConverter to use FrameList objects
*** DONE Convert ReplayGainReader to use FrameList objects
*** DONE Ensure integrated FrameList passes all unit tests
*** DONE Remove deprecated audiotools.FrameList object
*** DONE Remove deprecated pcmstream.PCMStreamReader object
*** DONE Convert pcmstream module to resample module
*** DONE Avoid importing audiotools.pcm so often
    Other C libraries often import audiotools.pcm via Python callbacks
    This library importing should be cached when possible.
    - [X] cdiomodule
    - [X] pcmreader
    - [X] replaygain
    - [X] resample
*** DONE Check for memory leaks
*** DONE Add FrameList and FloatFrameList programming documentation
*** DONE Remove .copy() method
    Since FrameLists are now immutable, there's no need for it
*** DONE Make pcm objects self-documenting
    For example, their methods and functions should give useful info
    when checked with "help()"
** DONE Add native ReplayGain handling routines
*** DONE Add native ReplayGain handling to FlacAudio/OggFlacAudio
*** DONE Ensure add_replay_gain()'s exceptions are caught
    Errors during calculation may raise ValueError,
    which must be caught anywhere the function is called
*** DONE Add ReplayGain unit tests
*** DONE Ensure ReplayGain works properly on 8bps and 24bps output
*** DONE Ensure ReplayGain is applied consistently
    Although cd2track and tracksplit are guaranteed
    to generate only one album at a time, track2track and tracktag are not.
    If multiple albums are applied gain at once,
    add_replay_gain must be called on an album-by-album basis
    rather than on the entire set.
*** DONE Double-check ReplayGainReader
    Ensure its output is consistent with other implementations.
** DONE Fix multi-channel audio handling
   It's important that channel mapping information be preserved
   when transcoding between sources with 3+ channels.
   This likely means another flag for PCMReader so that
   from_pcm() can build a file with the proper channel mask set.
   However, it may also be necessary to build some sort of
   channel reordering mechanism in the event that formats differ
   on how channels are to be ordered in the file.
*** Channel Counts and Ordering
| Format     | Maximum Channels | Ordering               |
|------------+------------------+------------------------|
| AAC        |               48 | stereo-only (via faac) |
| AIFF       |             2^16 | predefined             |
| Sun AU     |             2^32 | mostly undefined       |
| FLAC       |                8 | as WAVE                |
| M4A        |               48 | as WAVE?               |
| MP2        |                2 | stereo-only            |
| MP3        |                2 | stereo-only            |
| Musepack   |                2 | stereo-only            |
| Ogg FLAC   |                8 | as WAVE                |
| Ogg Vorbis |              255 | predefined             |
| Ogg Speex  |             2^32 | stereo-only            |
| RIFF WAVE  |             2^16 | predefined             |
| WavPack    |               16 | as WAVE                |
|------------+------------------+------------------------|
*** DONE Fix AudioFile definitions to support channel_mask()
    - [X] AACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] SpeexAudio
    - [X] WaveAudio
    - [X] WavPackAudio
*** DONE Fix to_pcm() methods to support channel_mask
    - [X] AACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] SpeexAudio
    - [X] WaveAudio
    - [X] WavPackAudio
*** DONE Fix from_pcm() classmethods to support channel_mask
    - [X] AACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] SpeexAudio
    - [X] WaveAudio
    - [X] WavPackAudio
*** DONE Fix alternate PCMReaders to support channel_mask
    - [X] BufferedPCMReader
    - [X] PCMConverter
    - [X] ReplayGainReader
    - [X] CDTrackReader
    - [X] OffsetCDTrackReader
    - [X] PCMCat
*** DONE Handle undefined channel masks in a sane way
**** DONE Fix to_pcm() methods to output undefined ChannelMasks
     If a format has not defined channel assignments for a given
     channel count, its to_pcm() method should return undefined ChannelMasks.
     - [X] AACAudio
     - [X] AiffAudio
     - [X] AuAudio
     - [X] FlacAudio
     - [X] M4AAudio
     - [X] MP2Audio
     - [X] MP3Audio
     - [X] OggFlacAudio
     - [X] VorbisAudio
     - [X] SpeexAudio
     - [X] WaveAudio
     - [X] WavPackAudio
**** DONE Fix from_pcm() classmethods to accept undefined ChannelMasks
     So long as the number of channels is acceptable,
     audio formats are free to place undefined ChannelMasks
     in whatever arrangement they'd like.
     - [X] AACAudio
     - [X] AiffAudio
     - [X] AuAudio
     - [X] FlacAudio
     - [X] M4AAudio
     - [X] MP2Audio
     - [X] MP3Audio
     - [X] OggFlacAudio
     - [X] VorbisAudio
     - [X] SpeexAudio
     - [X] WaveAudio
     - [X] WavPackAudio
*** DONE Unit test multichannel encoding and channel_mask handling
**** DONE Ensure all AudioFile types have a working channel_mask() method
     Even 2 channel audio should yield something valid.
**** DONE Ensure all to_pcm() methods yield a matching channel_mask attribute
**** DONE Ensure channel_mask is preserved between from_pcm(to_pcm()) calls
**** DONE Ensure channel_mask is preserved between to_wave()/from_wave() calls
**** DONE Ensure channels are actually stored in the proper order
     This is less of an issue for .wav, .flac, .oga or .wv
     which already store channels in RIFF WAVE order
     and more of an issue for Ogg Vorbis and other formats that do not.
**** DONE Ensure UnsupportedChannelMask is raised when necessary
     This includes calls to from_pcm() and from_wave()
*** DONE Ensure PCMReader.channel_mask is always an integer
** DONE Fix the unit test error messages
** DONE Make the programming documentation web-capable
    It should render consistently with the regular Python reference docs
    and be placed both in the source tree and on the website
    for better accessability.
*** DONE Document audiotools
     - [X] AudioFile
     - [X] BufferedPCMReader
     - [X] ChannelMask
     - [X] ExecQueue
     - [X] Image
     - [X] MetaData
     - [X] PCMConverter
     - [X] PCMReader
     - [X] PCMCat
     - [X] ReorderedPCMReader
     - [X] ReplayGain
     - [X] ReplayGainReader
     - [X] Messenger
     - [X] AlbumMetaData
     - [X] CDTrackLog
     - [X] CDDA
     - [X] CDTrackReader
     - [X] calculate_replay_gain
     - [X] filename_to_type
     - [X] find_glade_file
     - [X] group_tracks
     - [X] open
     - [X] open_directory
     - [X] open_files
     - [X] pcm_cmp
     - [X] pcm_split
     - [X] read_metadata_file
     - [X] read_sheet
     - [X] stripped_pcm_cmp
     - [X] transfer_data
     - [X] transfer_framelist_data
     - [X] BIN
     - [X] TYPE_MAP
     - [X] VERSION
     - [X] AVAILABLE_TYPES
*** DONE Document audiotools.pcm
     - [X] FloatFrameList
     - [X] FrameList
     - [X] from_channels
     - [X] from_float_channels
     - [X] from_float_frames
     - [X] from_frames
     - [X] from_list
*** DONE Document audiotools.resample
     - [X] Resampler
*** DONE Document audiotools.replaygain
     - [X] ReplayGain
*** DONE Document audiotools.cdio
     - [X] CDDA
     - [X] set_read_callback
*** DONE Document audiotools.cue
     - [X] Cuesheet
     - [X] read_cuesheet
     - [X] CueException
*** DONE Document audiotools.toc
     - [X] TOCFile
     - [X] read_tocfile
     - [X] TOCException
** DONE Make reference documentation render consistently
*** DONE Ensure documents render in letter and A4 size
*** DONE Add Creative Commons licensing to source code and doc itself
*** DONE Add internal PDF linkage
    The file should have working bookmarks
    and internal links so one can click directly to a chapter
*** DONE Add new introduction
*** DONE Basics
**** DONE Hexadecimal
**** DONE Endianness
**** DONE Signed values
     How to decode/encode signed integers should be properly explained
**** DONE Character Encodings
**** DONE PCM
*** DONE .wav
**** DONE the RIFF WAVE stream
**** DONE the fmt chunk
**** DONE the WAVEFORMATEXTENSIBLE fmt chunk
**** DONE the data chunk
**** DONE channel mapping
*** DONE .aiff
**** DONE the AIFF stream
**** DONE the COMM chunk
***** TODO 80 bit IEEE standard 754 floating point
**** DONE the SSND chunk
*** DONE .au
**** DONE the AU stream
**** DONE the AU header
*** DONE .flac
**** DONE the FLAC file stream
**** DONE FLAC metadata
***** DONE the PADDING metadata block
***** DONE the APPLICATION metadata block
***** DONE the SEEKTABLE metadata block
***** DONE the VORBIS_COMMENT metadata block
***** DONE the PICTURE metadata block
***** DONE the CUESHEET metadata block
**** DONE FLAC decoding
***** DONE the CONSTANT subframe
***** DONE the VERBATIM subframe
***** DONE the FIXED subframe
***** DONE the LPC subframe
***** DONE the Residual
****** DONE Rice Encoding
***** DONE Channels
***** DONE Wasted bits per sample
**** DONE FLAC encoding
***** DONE Metadata header
***** DONE the STREAMINFO metadata block
***** DONE Frame header
***** DONE Channel assignment
***** DONE Subframe header
***** DONE the CONSTANT subframe
***** DONE the VERBATIM subframe
***** DONE the FIXED subframe
***** DONE the LPC subframe
****** DONE Windowing
****** DONE Computing autocorrelation
****** DONE LP coefficient calculation
****** DONE Best order estimation
****** DONE Best order exhaustive search
****** DONE Quantizing coefficients
****** DONE Calculation Residual
***** DONE the Residual
****** DONE Residual Values
**** DONE the Checksums
***** TODO CRC-8
***** TODO CRC-16
*** DONE .ape
**** DONE the Monkey's Audio stream
**** DONE the APE Descriptor
**** DONE the APE Header
**** DONE the APEv2 tag
**** DONE the APEv2 tag header/footer
**** DONE the APEv2 flags
*** DONE .wv
**** DONE the WavPack file stream
**** DONE a WavPack block header
**** DONE a WavPack sub-block header
*** DONE .mp3
**** DONE the MP3 file stream
**** DONE an MPEG frame header
***** DONE the Xing header
**** DONE the ID3v1 tag
***** DONE ID3v1
***** DONE ID3v1.1
**** DONE the ID3v2 tag
***** DONE the ID3v2 stream
***** DONE ID3v2.2
****** DONE the ID3v2.2 Header
****** DONE an ID3v2.2 Frame
****** DONE ID3v2.2 Frame IDs
****** DONE the PIC Frame
***** DONE ID3v2.3
****** DONE the ID3v2.3 Header
****** DONE an ID3v2.3 Frame
****** DONE ID3v2.3 Frame IDs
****** DONE the APIC Frame
***** DONE ID3v2.4
****** DONE the ID3v2.4 Header
****** DONE an ID3v2.4 Frame
****** DONE ID3v2.4 Frame IDs
****** DONE the APIC Frame
*** DONE .ogg
**** DONE the Ogg file stream
**** DONE an Ogg Page
**** DONE Ogg packets
**** DONE the Identification packet
**** DONE the Comment packet
**** DONE Channel assignment
*** DONE .spx
**** DONE the Header packet
**** DONE the Comment packet
*** DONE .oga
**** DONE the Ogg FLAC file stream
**** DONE the STREAMINFO metadata packet
**** DONE the Metadata packets
*** DONE .m4a
**** DONE the QuickTime file stream
**** DONE a QuickTime atom
**** DONE Container atoms
**** DONE M4A atoms
***** DONE the ftyp atom
***** DONE the mvhd atom
***** DONE the tkhd atom
***** DONE the mdhd atom
***** DONE the hdlr atom
***** DONE the smhd atom
***** DONE the dref atom
***** DONE the stsd atom
***** DONE the mp4a atom
***** DONE the stts atom
***** DONE the stsc atom
***** DONE the stsz atom
***** DONE the stco atom
***** DONE the meta atom
****** DONE the trkn sub-atom
****** DONE the disk sub-atom
*** DONE .mpc
**** DONE Musepack SV7
***** DONE the Musepack SV7 file stream
***** DONE the Musepack SV7 header
**** DONE Musepack SV8
***** DONE the Musepack SV8 file stream
***** DONE Nut-encoded values
***** DONE the SH packet
***** DONE the SE packet
***** DONE the RG packet
***** DONE the EI packet
*** DONE FreeDB
**** DONE Native Protocol
***** DONE the Disc ID
***** DONE Initial Greeting
***** DONE Client-Server Handshake
***** DONE Set Protocol Level
***** DONE Query Database
***** DONE Read XMCD Data
***** DONE Close Connection
**** DONE Web Protocol
**** DONE XMCD
*** DONE MusicBrainz
**** DONE Searching Releases
***** DONE The Disc ID
***** DONE Server Query
***** DONE Release XML
**** DONE MusieBrainz XML
*** DONE ReplayGain
**** DONE Applying ReplayGain
**** DONE Calculating ReplayGain
***** DONE the Equal Loudness Filter
      This should be re-documented to be closer to the actual implementation
****** TODO the Yule Filter
****** TODO the Buffer Filter
****** TODO a Filtering Example
***** DONE RMS Energy Blocks
***** DONE Statistical Processing and Calibration
*** DONE References
*** DONE Remove old troff reference documentation
*** DONE Add title and author to PDF documentation
** DONE Ensure make(1) from doc/ directory builds both doc trees
** DONE Seperate unreadable files from unknown files
   Files we're unable to read should be handled differently
   from files we're unable to understand.
*** DONE Update audiotools.open() to raise IOErrors
*** DONE Update audiotools.open_files() to handle IOErrors
*** DONE Update audiotools.open_directory() to handle IOErrors
*** DONE Update internal calls to open()/open_files() to handle IOErrors
    - [X] __flac__.py
    - [X] __m4a__.py
    - [X] __mp3__.py
    - [X] __vorbis__.py
    - [X] __wavpack__.py
    - [X] __wav__.py
*** DONE Update tools which use open()/open_files() to handle IOErrors
    - [X] coverdump
    - [X] editxmcd
    - [X] track2cd
    - [X] track2track
    - [X] track2xmcd
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
*** DONE Add unit tests to demonstrate new behavior
*** DONE Update programming documentation with new behavior
** DONE Ensure embedded cuesheets aren't clobbered by adding more metadata
** DONE Adjust %(album_track_number)s to accomodate more than 9 albums
   For example, track 2 of 7, album 5 of 11 in format
   "%(album_track_number)s - %(track_name)s.%(suffix)s should be:
   "0502 - name.suffix"
** DONE editxmcd's "New" command should work with embedded cuesheets
   Selecting a single disc image with embedded cuesheets should
   fill in the proper XMCD/MusicBrainz fields
** DONE Ensure add_replay_gain used on hi-def tracks doesn't raise errors
** DONE Add Shorten support
   I don't expect people have a lot of shn files lying around
   and nobody should be using it for new data
   (though I'll add a rudimentary encoder for completeness' sake)
   but it's interesting to document for historical reasons.
*** DONE Build complete decoder
    - [X] DIFF0
    - [X] DIFF1
    - [X] DIFF2
    - [X] DIFF3
    - [X] QUIT
    - [X] BLOCKSIZE
    - [X] BITSHIFT
    - [X] QLPC
    - [X] ZERO
    - [X] VERBATIM
    I'll likely limit support to Shorten version2/3
    since generating older versions will be a challenge
    and this format is obscure enough as it is.
*** DONE Build partial encoder
    - [X] DIFF0
    - [X] DIFF1
    - [X] DIFF2
    - [X] QUIT
    - [X] BLOCKSIZE
    - [X] ZERO
    - [X] VERBATIM
**** DONE Convert partial encoder for standalone use
     To ensure there aren't any memory leaks
*** DONE Add ShortenAudio type to audiotools Python core
*** DONE Add Shorten-specific unit tests
*** DONE Document Shorten
    - [X] Shorten data types
    - [X] the Shorten file stream
    - [X] the decoding process
    - [X] the encoding process
** DONE trackcmp should give exact PCM frame/byte of first mismatch
*** DONE update unit tests to cover new behavior
    - [X] test_trackcmp
    - [X] test_trackcmp1
    - [X] test_trackcmp2
    - [X] test_trackcmp3
    - [X] test_trackcmp4
*** DONE update manual page to cover new behavior
** DONE Add analyzers for built-in decoders
   Analagous to flac(1)'s --analyze option,
   this will be an .analyze_frame() method that returns a Python dict
   of ints/lists/dicts containing frame data on each pass,
   or None at the stream's end.
   This will provide both an easier way to visualize the file's internals,
   and also a debugging aid.
*** DONE FlacDecoder
*** DONE SHNDecoder
*** DONE ALACDecoder
** DONE Add more examples
   A lot of handy new features aren't documented with examples and walkthroughs.
   Examples to add include:
- [X] a full multi-CD example, detailing the use of --album-number
- [X] an image embedding walkthrough
- [X] a CD image creation, splitting, burning example involving TOC/CUE files
- [X] an XMCD walkthrough with fetching, editing and tagging
** DONE Update documentation to cover concrete MetaData classes
   - [X] ApeTag
   - [X] FlacMetaData
   - [X] ID3v1Comment
   - [X] ID3v22Comment
   - [X] ID3v23Comment
   - [X] ID3v24Comment
   - [X] ID3CommentPair
   - [X] M4AMetaData
   - [X] VorbisComment
** DONE Ensure man pages install correctly on Mac OS X
** DONE add a %(basename)s --format attribute
   For example, given the path: "/foo/bar/01 - track name.mp3"
   the %(basename)s attribute would be: "01 - track name"
   allowing one to ignore its internal metadata entirely
   and use original names.
*** DONE Update AudioFile.track_name to support attribute
*** DONE Update tools that call AudioFile.track_name
    - [X] cd2track
    - [X] track2track
    - [X] trackrename
    - [X] tracksplit
*** DONE Update man pages for tools that call AudioFile.track_name
    - [X] track2track.1
    - [X] trackrename.1
    - [X] audiotools.cfg.5
*** DONE Add unit test support for new attribute
*** DONE Add documentation for new field to programming reference
*** DONE Add documentation for format strings to programming reference
** DONE Update AudioFile.track_name classmethod
   the previous behavior was a kludge cobbled together over time
   Its new call method is:  track_name(file_path, metadata, format)
   so that track_number and album_number can be pulled from file_path
   directly instead of passed in from outside.
*** DONE Update tools to support new calling method
    - [X] track2track
    - [X] cd2track
    - [X] trackrename
    - [X] tracksplit
*** DONE Update programming documentation
*** DONE Add unit tests
*** DONE Update old unit tests with new behavior
*** DONE unit test suffix field
** DONE Update Python code to support PEP 8
   Following accepted Python style should make the code more
   accessible and maintainable in the long run.
   It's also a good opportunity to clean up and simplify code
   without changing the actual API interface.
*** DONE Update core modules
**** DONE __aiff__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] IEEE_Extended
      - [X] AiffException
      - [X] AiffAudio
      - [X] AiffAudio.bits_per_sample
      - [X] AiffAudio.channels
      - [X] AiffAudio.channel_mask
      - [X] AiffAudio.lossless
      - [X] AiffAudio.total_frames
      - [X] AiffAudio.sample_rate
      - [X] AiffAudio.is_type
      - [X] AiffAudio.chunks
      - [X] AiffAudio.comm_chunk
      - [X] AiffAudio.chunk_files
      - [X] AiffAudio.get_metadata
      - [X] AiffAudio.set_metadata
      - [X] AiffAudio.delete_metadata
      - [X] AiffAudio.to_pcm
      - [X] AiffAudio.from_pcm
      - [X] AIFFChannelMask
      - [X] AIFFChannelMask.channels
**** DONE __ape__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] ApeTagItem
      - [X] ApeTagItem.build
      - [X] ApeTagItem.binary
      - [X] ApeTagItem.external
      - [X] ApeTagItem.string
      - [X] ApeTag
      - [X] ApeTag.converted
      - [X] ApeTag.merge
      - [X] ApeTag.supports_images
      - [X] ApeTag.add_image
      - [X] ApeTag.delete_image
      - [X] ApeTag.images
      - [X] ApeTag.read
      - [X] ApeTag.build
      - [X] ApeTaggedAudio
      - [X] ApeTaggedAudio.get_metadata
      - [X] ApeTaggedAudio.set_metadata
      - [X] ApeTaggedAudio.delete_metadata
      - [X] ApeAudio
      - [X] ApeAudio.is_type
      - [X] ApeAudio.lossless
      - [X] ApeAudio.supports_foreign_riff_chunks
      - [X] ApeAudio.has_foreign_riff_chunks
      - [X] ApeAudio.bits_per_sample
      - [X] ApeAudio.channels
      - [X] ApeAudio.total_frames
      - [X] ApeAudio.sample_rate
      - [X] ApeAudio.to_wave
      - [X] ApeAudio.from_wave
**** DONE __au__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] AuAudio
      - [X] AuAudio.is_type
      - [X] AuAudio.lossless
      - [X] AuAudio.bits_per_sample
      - [X] AuAudio.channels
      - [X] AuAudio.channel_mask
      - [X] AuAudio.sample_rate
      - [X] AuAudio.total_frames
      - [X] AuAudio.to_pcm
      - [X] AuAudio.from_pcm
      - [X] AuAudio.track_name
**** DONE __flac__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [ ] FlacException
      - [X] FlacMetaDataBlockTooLarge
      - [X] FlacMetaDataBlock
      - [X] FlacMetaDataBlock.build_block
      - [X] FlacMetaData
      - [X] FlacMetaData.converted
      - [X] FlacMetaData.merge
      - [X] FlacMetaData.add_image
      - [X] FlacMetaData.delete_image
      - [X] FlacMetaData.images
      - [X] FlacMetaData.metadata_blocks
      - [X] FlacMetaData.build
      - [X] FlacMetaData.supports_images
      - [X] FlacVorbisComment
      - [X] FlacVorbisComment.build_block
      - [X] FlacVorbisComment.converted
      - [X] FlacPictureComment
      - [X] FlacPictureComment.converted
      - [X] FlacPictureComment.type_string
      - [X] FlacPictureComment.build
      - [X] FlacPictureComment.build_block
      - [X] FlacCueSheet
      - [X] FlacCueSheet.build_block
      - [X] FlacCueSheet.converted
      - [X] FlacCueSheet.catalog
      - [X] FlacCueSheet.ISRCs
      - [X] FlacCueSheet.indexes
      - [X] FlacCueSheet.pcm_lengths
      - [X] FlacAudio
      - [X] FlacAudio.is_type
      - [X] FlacAudio.channel_mask
      - [X] FlacAudio.lossless
      - [X] FlacAudio.supports_foreign_riff_chunks
      - [X] FlacAudio.get_metadata
      - [X] FlacAudio.set_metadata
      - [X] FlacAudio.metadata_length
      - [X] FlacAudio.delete_metadata
      - [X] FlacAudio.set_cuesheet
      - [X] FlacAudio.get_cuesheet
      - [X] FlacAudio.to_pcm
      - [X] FlacAudio.from_pcm
      - [X] FlacAudio.has_foreign_riff_chunks
      - [X] FlacAudio.riff_wave_chunks
      - [X] FlacAudio.to_wave
      - [X] FlacAudio.from_wave
      - [X] FlacAudio.bits_per_sample
      - [X] FlacAudio.channels
      - [X] FlacAudio.total_frames
      - [X] FlacAudio.sample_rate
      - [X] FlacAudio.add_replay_gain
      - [X] FlacAudio.can_add_replay_gain
      - [X] FlacAudio.lossless_replay_gain
      - [X] FlacAudio.replay_gain
      - [X] FlacAudio.sub_pcm_tracks
      - [X] OggFlacAudio
      - [X] OggFlacAudio.is_type
      - [X] OggFlacAudio.get_metadata
      - [X] OggFlacAudio.set_metadata
      - [X] OggFlacAudio.metadata_length
      - [X] OggFlacAudio.to_pcm
      - [X] OggFlacAudio.from_pcm
      - [X] OggFlacAudio.sub_pcm_tracks
      - [X] OggFlacAudio.supports_foreign_riff_chunks
**** DONE __freedb__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] XMCDException
      - [X] XMCD
      - [X] XMCD.key_digits
      - [X] XMCD.build
      - [X] XMCD.read
      - [X] XMCD.read_data
      - [X] XMCD.from_files
      - [X] XMCD.from_cuesheet
      - [X] XMCD.metadata
      - [X] DiscID
      - [X] DiscID.from_cdda
      - [X] DiscID.add
      - [X] DiscID.offsets
      - [X] DiscID.length
      - [X] DiscID.idsuffix
      - [X] DiscID.freedb_id
      - [X] DiscID.toxmcd
      - [X] FreeDBException
      - [X] FreeDB
      - [X] FreeDB.connect
      - [X] FreeDB.close
      - [X] FreeDB.write
      - [X] FreeDB.read
      - [X] FreeDB.query
      - [X] FreeDB.read_data
      - [X] FreeDBWeb
      - [X] FreeDBWeb.connect
      - [X] FreeDBWeb.close
      - [X] FreeDBWeb.write
      - [X] FreeDBWeb.read
      - [X] FreeDBWeb.query
      - [X] FreeDBWeb.read_data
      - [X] get_xmcd
**** DONE __id3__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] UCS2Codec
      - [X] UCS2Codec.fix_char
      - [X] UCS2Codec.encode
      - [X] UCS2Codec.decode
      - [X] UnsupportedID3v2Version
      - [X] Syncsafe32
      - [X] UBInt24
      - [X] WidecharCStringAdapter
      - [X] UCS2CString
      - [X] UTF16CString
      - [X] UTF16BECString
      - [X] ID3v22Frame
      - [X] ID3v22Frame.build
      - [X] ID3v22Frame.parse
      - [X] ID3v22TextFrame
      - [X] ID3v22TextFrame.total
      - [X] ID3v22TextFrame.from_unicode
      - [X] ID3v22TextFrame.build
      - [X] ID3v22ComFrame
      - [X] ID3v22ComFrame.from_unicode
      - [X] ID3v22ComFrame.build
      - [X] ID3v22PicFrame
      - [X] ID3v22PicFrame.type_string
      - [X] ID3v22PicFrame.build
      - [X] ID3v22PicFrame.converted
      - [X] ID3v22Comment
      - [X] ID3v22Comment.add_image
      - [X] ID3v22Comment.delete_image
      - [X] ID3v22Comment.images
      - [X] ID3v22Comment.parse
      - [X] ID3v22Comment.converted
      - [X] ID3v22Comment.merge
      - [X] ID3v22Comment.build
      - [X] ID3v22Comment.skip
      - [X] ID3v22Comment.read_id3v2_comment
      - [X] ID3v23Frame
      - [X] ID3v23Frame.build
      - [X] ID3v23Frame.parse
      - [X] ID3v23TextFrame
      - [X] ID3v23TextFrame.total
      - [X] ID3v23TextFrame.from_unicode
      - [X] ID3v23TextFrame.build
      - [X] ID3v23PicFrame
      - [X] ID3v23PicFrame.build
      - [X] ID3v23PicFrame.converted
      - [X] ID3v23ComFrame
      - [X] ID3v23ComFrame.from_unicode
      - [X] ID3v23ComFrame.build
      - [X] ID3v23Comment
      - [X] ID3v23Comment.add_image
      - [X] ID3v23Comment.delete_image
      - [X] ID3v23Comment.images
      - [X] ID3v23Comment.build
      - [X] ID3v24Frame
      - [X] ID3v24Frame.build
      - [X] ID3v24Frame.parse
      - [X] ID3v24TextFrame
      - [X] ID3v24TextFrame.total
      - [X] ID3v24TextFrame.from_unicode
      - [X] ID3v24TextFrame.build
      - [X] ID3v24PicFrame
      - [X] ID3v24PicFrame.build
      - [X] ID3v24PicFrame.converted
      - [X] ID3v24ComFrame
      - [X] ID3v24ComFrame.from_unicode
      - [X] ID3v24ComFrame.build
      - [X] ID3v24Comment
      - [X] ID3v24Comment.build
      - [X] ID3CommentPair
      - [X] ID3CommentPair.converted
      - [X] ID3CommentPair.merge
      - [X] ID3CommentPair.images
      - [X] ID3CommentPair.add_image
      - [X] ID3CommentPair.delete_image
      - [X] ID3CommentPair.supports_images
**** DONE __id3v1__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] ID3v1Comment
      - [X] ID3v1Comment.read_id3v1_comment
      - [X] ID3v1Comment.build_id3v1
      - [X] ID3v1Comment.supports_images
      - [X] ID3v1Comment.converted
      - [X] ID3v1Comment.build_tag
      - [X] ID3v1Comment.images
**** DONE __image__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] image_metrics
      - [X] ImageMetrics
      - [X] InvalidImage
      - [X] InvalidJPEG
      - [X] InvalidPNG
      - [X] InvalidBMP
      - [X] InvalidGIF
      - [X] InvalidTIFF
      - [X] can_thumbnail
      - [X] thumbnail_formats
      - [X] thumbnail_image
**** DONE __init__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] __init__.py
      - [X] RawConfigParser
      - [X] RawConfigParser.get_default
      - [X] RawConfigParser.getint_default
      - [X] find_glade_file
      - [X] OptionParser
      - [X] Messenger
      - [X] str_width
      - [X] VerboseMessenger
      - [X] VerboseMessenger.output
      - [X] VerboseMessenger.partial_output
      - [X] VerboseMessenger.new_row
      - [X] VerboseMessenger.blank_row
      - [X] VerboseMessenger.divider_row
      - [X] VerboseMessenger.output_column
      - [X] VerboseMessenger.output_rows
      - [X] VerboseMessenger.info
      - [X] VerboseMessenger.partial_info
      - [X] VerboseMessenger.error
      - [X] VerboseMessenger.warning
      - [X] VerboseMessenger.usage
      - [X] VerboseMessenger.filename
      - [X] VerboseMessenger.ansi
      - [X] VerboseMessenger.ansi_err
      - [X] SilentMessenger
      - [X] SilentMessenger.output
      - [X] SilentMessenger.partial_output
      - [X] SilentMessenger.warning
      - [X] SilentMessenger.info
      - [X] SilentMessenger.partial_info
      - [X] UnsupportedFile
      - [X] InvalidFile
      - [X] InvalidFormat
      - [X] EncodingError
      - [X] UnsupportedChannelMask
      - [X] UnsupportedChannelCount
      - [X] DecodingError
      - [X] open
      - [X] open_files
      - [X] open_directory
      - [X] UnknownAudioType
      - [X] AmbiguousAudioType
      - [X] filename_to_type
      - [X] ChannelMask
      - [X] ChannelMask.defined
      - [X] ChannelMask.undefined
      - [X] ChannelMask.channels
      - [X] ChannelMask.index
      - [X] ChannelMask.from_fields
      - [X] ChannelMask.from_channels
      - [X] PCMReader
      - [X] PCMReader.read
      - [X] PCMReader.close
      - [X] PCMReaderError
      - [X] PCMReaderError.read
      - [X] PCMReaderError.close
      - [X] ReorderedPCMReader
      - [X] ReorderedPCMReader.read
      - [X] ReorderedPCMReader.close
      - [X] transfer_data
      - [X] transfer_framelist_data
      - [X] threaded_transfer_framelist_data
      - [X] pcm_cmp
      - [X] stripped_pcm_cmp
      - [X] pcm_frame_cmp
      - [X] PCMCat
      - [X] PCMCat.read
      - [X] PCMCat.close
      - [X] BufferedPCMReader
      - [X] BufferedPCMReader.close
      - [X] BufferedPCMReader.read
      - [X] pcm_split
      - [X] PCMConverter
      - [X] PCMConverter.read
      - [X] PCMConverter.close
      - [X] applicable_replay_gain
      - [X] calculate_replay_gain
      - [X] InterruptableReader
      - [X] InterruptableReader.stop
      - [X] InterruptableReader.send_data
      - [X] InterruptableReader.read
      - [X] ignore_sigint
      - [X] make_dirs
      - [X] MetaData
      - [X] MetaData.converted
      - [X] MetaData.supports_images
      - [X] MetaData.images
      - [X] MetaData.front_covers
      - [X] MetaData.back_covers
      - [X] MetaData.leaflet_pages
      - [X] MetaData.media_images
      - [X] MetaData.other_images
      - [X] MetaData.add_image
      - [X] MetaData.delete_image
      - [X] MetaData.merge
      - [X] AlbumMetaData
      - [X] AlbumMetaData.metadata
      - [X] MetaDataFileException
      - [X] Image
      - [X] Image.suffix
      - [X] Image.type_string
      - [X] Image.new
      - [X] Image.thumbnail
      - [X] ReplayGain
      - [X] UnsupportedTracknameField
      - [X] AudioFile
      - [X] AudioFile.is_type
      - [X] AudioFile.bits_per_sample
      - [X] AudioFile.channels
      - [X] AudioFile.channel_mask
      - [X] AudioFile.lossless
      - [X] AudioFile.set_metadata
      - [X] AudioFile.get_metadata
      - [X] AudioFile.delete_metadata
      - [X] AudioFile.total_frames
      - [X] AudioFile.cd_frames
      - [X] AudioFile.sample_rate
      - [X] AudioFile.to_pcm
      - [X] AudioFile.from_pcm
      - [X] AudioFile.to_wave
      - [X] AudioFile.from_wave
      - [X] AudioFile.supports_foreign_riff_chunks
      - [X] AudioFile.has_foreign_riff_chunks
      - [X] AudioFile.track_number
      - [X] AudioFile.album_number
      - [X] AudioFile.track_name
      - [X] AudioFile.add_replay_gain
      - [X] AudioFile.can_add_replay_gain
      - [X] AudioFile.lossless_replay_gain
      - [X] AudioFile.replay_gain
      - [X] AudioFile.set_cuesheet
      - [X] AudioFile.get_cuesheet
      - [X] AudioFile.has_binaries
      - [X] DummyAudioFile
      - [X] DummyAudioFile.get_metadata
      - [X] DummyAudioFile.cd_frames
      - [X] DummyAudioFile.track_number
      - [X] DummyAudioFile.sample_rate
      - [X] DummyAudioFile.total_frames
      - [X] SheetException
      - [X] read_sheet
      - [X] parse_timestamp
      - [X] build_timestamp
      - [X] sheet_to_unicode
      - [X] at_a_time
      - [X] CDDA
      - [X] RawCDDA
      - [X] RawCDDA.length
      - [X] RawCDDA.close
      - [X] RawCDDA.first_sector
      - [X] RawCDDA.last_sector
      - [X] OffsetCDDA
      - [X] OffsetCDDA.close
      - [X] CDTrackLog
      - [X] CDTrackReader
      - [X] CDTrackReader.offset
      - [X] CDTrackReader.length
      - [X] CDTrackReader.log
      - [X] CDTrackReader.read
      - [X] CDTrackReader.close
      - [X] OffsetCDTrackReader
      - [X] OffsetCDTrackReader.offset
      - [X] OffsetCDTrackReader.length
      - [X] OffsetCDTrackReader.log
      - [X] OffsetCDTrackReader.read
      - [X] OffsetCDTrackReader.close
      - [X] read_metadata_file
      - [X] ExecQueue
      - [X] ExecQueue.execute
      - [X] ExecQueue.run
      - [X] BitstreamReader
      - [X] BitstreamReader.byte_align
      - [X] BitstreamReader.read
      - [X] BitstreamReader.unread
      - [X] BitstreamReader.read_signed
      - [X] BitstreamReader.unary
      - [X] BitstreamReader.tell
      - [X] BitstreamReader.close
      - [X] BitstreamWriter
      - [X] BitstreamWriter.byte_align
      - [X] BitstreamWriter.write
      - [X] BitstreamWriter.write_signed
      - [X] BitstreamWriter.unary
      - [X] BitstreamWriter.tell
      - [X] BitstreamWriter.close
**** DONE __m4a_atoms__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] VersionLength
      - [X] AtomAdapter
      - [X] Atom
      - [X] AtomListAdapter
      - [X] AtomContainer
      - [X] AtomWrapper
**** DONE __m4a__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] M4AAudio_faac
      - [X] M4AAudio_faac.channel_mask
      - [X] M4AAudio_faac.is_type
      - [X] M4AAudio_faac.lossless
      - [X] M4AAudio_faac.channels
      - [X] M4AAudio_faac.bits_per_sample
      - [X] M4AAudio_faac.sample_rate
      - [X] M4AAudio_faac.cd_frames
      - [X] M4AAudio_faac.total_frames
      - [X] M4AAudio_faac.get_metadata
      - [X] M4AAudio_faac.set_metadata
      - [X] M4AAudio_faac.delete_metadata
      - [X] M4AAudio_faac.to_pcm
      - [X] M4AAudio_faac.from_pcm
      - [X] M4AAudio_faac.can_add_replay_gain
      - [X] M4AAudio_faac.lossless_replay_gain
      - [X] M4AAudio_faac.add_replay_gain
      - [X] M4AAudio_nero
      - [X] M4AAudio_nero.to_pcm
      - [X] M4AAudio_nero.from_pcm
      - [X] M4AAudio_nero.to_wave
      - [X] M4AAudio_nero.from_wave
      - [X] ILST_Atom
      - [X] M4AMetaData
      - [X] M4AMetaData.binary_atom
      - [X] M4AMetaData.text_atom
      - [X] M4AMetaData.trkn_atom
      - [X] M4AMetaData.disk_atom
      - [X] M4AMetaData.covr_atom
      - [X] M4AMetaData.images
      - [X] M4AMetaData.add_image
      - [X] M4AMetaData.delete_image
      - [X] M4AMetaData.converted
      - [X] M4AMetaData.merge
      - [X] M4AMetaData.to_atom
      - [X] M4AMetaData.supports_images
      - [X] M4ACovr
      - [X] M4ACovr.converted
      - [X] ALACAudio
      - [X] ALACAudio.is_type
      - [X] ALACAudio.total_frames
      - [X] ALACAudio.channel_mask
      - [X] ALACAudio.cd_frames
      - [X] ALACAudio.lossless
      - [X] ALACAudio.to_pcm
      - [X] ALACAudio.from_pcm
      - [X] ALACAudio.to_wave
      - [X] ALACAudio.from_wave
      - [X] ADTSException
      - [X] AACAudio
      - [X] AACAudio.is_type
      - [X] AACAudio.bits_per_sample
      - [X] AACAudio.channels
      - [X] AACAudio.lossless
      - [X] AACAudio.total_frames
      - [X] AACAudio.sample_rate
      - [X] AACAudio.to_pcm
      - [X] AACAudio.from_pcm
      - [X] AACAudio.aac_frames
      - [X] AACAudio.aac_frame_count
**** DONE __mp3__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] MP3Exception
      - [X] MP3Audio
      - [X] MP3Audio.is_type
      - [X] MP3Audio.lossless
      - [X] MP3Audio.to_pcm
      - [X] MP3Audio.from_pcm
      - [X] MP3Audio.bits_per_sample
      - [X] MP3Audio.channels
      - [X] MP3Audio.sample_rate
      - [X] MP3Audio.get_metadata
      - [X] MP3Audio.set_metadata
      - [X] MP3Audio.delete_metadata
      - [X] MP3Audio.cd_frames
      - [X] MP3Audio.total_frames
      - [X] MP3Audio.can_add_replay_gain
      - [X] MP3Audio.lossless_replay_gain
      - [X] MP3Audio.add_replay_gain
      - [X] MP2Audio
      - [X] MP2Audio.is_type
      - [X] MP2Audio.from_pcm
**** DONE __musepack__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] NutValue
      - [X] Musepack8StreamReader
      - [X] Musepack8StreamReader.packets
      - [X] MusepackAudio
      - [X] MusepackAudio.from_pcm
      - [X] MusepackAudio.is_type
      - [X] MusepackAudio.sample_rate
      - [X] MusepackAudio.total_frames
      - [X] MusepackAudio.channels
      - [X] MusepackAudio.bits_per_sample
      - [X] MusepackAudio.lossless
**** DONE __musicbrainz__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] get_xml_nodes
      - [X] get_xml_text_node
      - [X] reorder_xml_children
      - [X] MBDiscID
      - [X] MBDiscID.from_cdda
      - [X] MBDiscID.offsets
      - [X] MusicBrainz
      - [X] MusicBrainz.connect
      - [X] MusicBrainz.close
      - [X] MusicBrainz.read_data
      - [X] MBXMLException
      - [X] MusicBrainzReleaseXML
      - [X] MusicBrainzReleaseXML.read
      - [X] MusicBrainzReleaseXML.read_data
      - [X] MusicBrainzReleaseXML.metadata
      - [X] MusicBrainzReleaseXML.from_files
      - [X] MusicBrainzReleaseXML.from_cuesheet
      - [X] MusicBrainzReleaseXML.build
      - [X] get_mbxml
**** DONE __shn__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] ShortenAudio
      - [X] ShortenAudio.is_type
      - [X] ShortenAudio.bits_per_sample
      - [X] ShortenAudio.channels
      - [X] ShortenAudio.channel_mask
      - [X] ShortenAudio.lossless
      - [X] ShortenAudio.total_frames
      - [X] ShortenAudio.sample_rate
      - [X] ShortenAudio.to_pcm
      - [X] ShortenAudio.from_pcm
      - [X] ShortenAudio.to_wave
      - [X] ShortenAudio.from_wave
      - [X] ShortenAudio.supports_foreign_riff_chunks
      - [X] ShortenAudio.has_foreign_riff_chunks
**** DONE __speex__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] UnframedVorbisComment
      - [X] SpeexAudio
      - [X] SpeexAudio.is_type
      - [X] SpeexAudio.to_pcm
      - [X] SpeexAudio.from_pcm
      - [X] SpeexAudio.set_metadata
      - [X] SpeexAudio.can_add_replay_gain
**** DONE __vorbiscomment__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] VorbisComment
      - [X] VorbisComment.supports_images
      - [X] VorbisComment.images
      - [X] VorbisComment.converted
      - [X] VorbisComment.merge
      - [X] VorbisComment.build
**** DONE __vorbis__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] OggStreamReader
      - [X] OggStreamReader.close
      - [X] OggStreamReader.packets
      - [X] OggStreamReader.pages
      - [X] OggStreamReader.pages_to_packets
      - [X] OggStreamReader.calculate_ogg_checksum
      - [X] OggStreamWriter
      - [X] OggStreamWriter.close
      - [X] OggStreamWriter.write_page
      - [X] OggStreamWriter.build_pages
      - [X] VorbisAudio
      - [X] VorbisAudio.is_type
      - [X] VorbisAudio.lossless
      - [X] VorbisAudio.bits_per_sample
      - [X] VorbisAudio.channels
      - [X] VorbisAudio.channel_mask
      - [X] VorbisAudio.total_frames
      - [X] VorbisAudio.sample_rate
      - [X] VorbisAudio.to_pcm
      - [X] VorbisAudio.from_pcm
      - [X] VorbisAudio.set_metadata
      - [X] VorbisAudio.get_metadata
      - [X] VorbisAudio.delete_metadata
      - [X] VorbisAudio.add_replay_gain
      - [X] VorbisAudio.can_add_replay_gain
      - [X] VorbisAudio.lossless_replay_gain
      - [X] VorbisAudio.replay_gain
      - [X] VorbisChannelMask
      - [X] VorbisChannelMask.channels
**** DONE __wavpack__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] SymlinkPCMReader
      - [X] SymlinkPCMReader.read
      - [X] SymlinkPCMReader.close
      - [X] SymlinkPCMReader.new
      - [X] WavePackAPEv2
      - [X] WavePackAPEv2.converted
      - [X] WavPackAudio
      - [X] WavPackAudio.is_type
      - [X] WavPackAudio.lossless
      - [X] WavPackAudio.supports_foreign_riff_chunks
      - [X] WavPackAudio.channel_mask
      - [X] WavPackAudio.get_metadata
      - [X] WavPackAudio.has_foreign_riff_chunks
      - [X] WavPackAudio.frames
      - [X] WavPackAudio.sub_frames
      - [X] WavPackAudio.bits_per_sample
      - [X] WavPackAudio.channels
      - [X] WavPackAudio.total_frames
      - [X] WavPackAudio.sample_rate
      - [X] WavPackAudio.from_pcm
      - [X] WavPackAudio.to_wave
      - [X] WavPackAudio.to_pcm
      - [X] WavPackAudio.from_wave
      - [X] WavPackAudio.add_replay_gain
      - [X] WavPackAudio.can_add_replay_gain
      - [X] WavPackAudio.replay_gain
      - [X] WavPackAudio.get_cuesheet
      - [X] WavPackAudio.set_cuesheet
**** DONE __wav__.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] WaveReader
      - [X] WaveReader.read
      - [X] WaveReader.close
      - [X] TempWaveReader
      - [X] TempWaveReader.close
      - [X] WavException
      - [X] WaveAudio
      - [X] WaveAudio.is_type
      - [X] WaveAudio.lossless
      - [X] WaveAudio.supports_foreign_riff_chunks
      - [X] WaveAudio.has_foreign_riff_chunks
      - [X] WaveAudio.channel_mask
      - [X] WaveAudio.to_pcm
      - [X] WaveAudio.from_pcm
      - [X] WaveAudio.to_wave
      - [X] WaveAudio.from_wave
      - [X] WaveAudio.total_frames
      - [X] WaveAudio.sample_rate
      - [X] WaveAudio.channels
      - [X] WaveAudio.bits_per_sample
      - [X] WaveAudio.can_add_replay_gain
      - [X] WaveAudio.lossless_replay_gain
      - [X] WaveAudio.add_replay_gain
      - [X] WaveAudio.track_name
      - [X] WaveAudio.fmt_chunk_to_channel_mask
      - [X] WaveAudio.chunk_ids
      - [X] WaveAudio.chunks
      - [X] WaveAudio.wave_from_chunks
      - [X] WaveAudio.pcm_split
**** DONE cue.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] cue.py
      - [X] CueException
      - [X] tokens
      - [X] get_value
      - [X] parse
      - [X] Cuesheet
      - [X] Cuesheet.catalog
      - [X] Cuesheet.single_file_type
      - [X] Cuesheet.indexes
      - [X] Cuesheet.pcm_lengths
      - [X] Cuesheet.ISRCs
      - [X] Cuesheet.file
      - [X] Track
      - [X] Track.ISRC
      - [X] read_cuesheet
**** DONE toc.py
***** DONE adjust syntax
***** DONE add docstrings
      - [X] toc.py
      - [X] TOCException
      - [X] parse
      - [X] TOCFile
      - [X] TOCFile.catalog
      - [X] TOCFile.indexes
      - [X] TOCFile.pcm_lengths
      - [X] TOCFile.ISRCs
      - [X] TOCFile.file
      - [X] Track
      - [X] Track.ISRC
      - [X] read_tocfile
*** DONE Update utilities
    - [X] audiotools-config
    - [X] cd2track
    - [X] cd2xmcd
    - [X] cdinfo
    - [X] coverdump
    - [X] coverview
    - [X] editxmcd
    - [X] record2track
    - [X] track2cd
    - [X] track2track
    - [X] track2xmcd
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
*** DONE Update tests
    - [X] test.py
    - [X] test_streams.py
** DONE Update C code to support PEP 7
   - [X] array.c
   - [X] array.h
   - [X] bitstream_r.c
   - [X] bitstream_r.h
   - [X] bitstream_w.c
   - [X] bitstream_w.h
   - [X] cdiomodule.c
   - [X] decoders.c
   - [X] decoders.h
   - [X] encoders.c
   - [X] encoders.h
   - [X] md5.c
   - [X] md5.h
   - [X] pcm.c
   - [X] pcm.h
   - [X] pcmreader.c
   - [X] pcmreader.h
   - [X] replaygain.c
   - [X] replaygain.h
   - [X] resample.c
   - [X] resample.h
   - [X] decoders/alaac.c
   - [X] decoders/alac.h
   - [X] decoders/flac.c
   - [X] decoders/flac_crc.c
   - [X] decoders/flac_crc.h
   - [X] decoders/flac.h
   - [X] decoders/shn.c
   - [X] decoders/shn.h
   - [X] encoders/alac.c
   - [X] encoders/alac.h
   - [X] encoders/alac_lpc.c
   - [X] encoders/alac_lpc.h
   - [X] encoders/flac.c
   - [X] encoders/flac.h
   - [X] encoders/flac_lpc.c
   - [X] encoders/flac_lpc.h
   - [X] encoders/shn.c
   - [X] encoders/shn.h
** DONE Add ALAC support
*** DONE Build ALAC decoder
**** DONE Add audiotools.decoders.AlacDecoder object
**** DONE Add frame header parsing
**** DONE Add subframe header parsing
**** DONE Add wasted-bits block parsing
**** DONE Add residual decoding
**** DONE Ensure frame footer is checked
**** DONE Decode subframes
**** DONE Perform channel decorrelation
     - [X] leftweight zero
     - [X] leftweight nonzero
**** DONE Handle wasted-bits samples
**** DONE Return pcm.FrameList objects on calls to read()
**** DONE Ensure PCMReader-compatible fields are present
     - [X] sample_rate
     - [X] channels
     - [X] channel_mask
     - [X] bits_per_sample
**** DONE Ensure 16/24 bps streams work correctly
     - [X] 16bps
     - [X] 24bps
**** DONE Ensure 1/2 channel streams work correctly
     - [X] 1 channel
     - [X] 2 channels
**** DONE Ensure different sample rates work correctly
     - [X] 44100Hz
     - [X] 48000Hz
     - [X] 96000Hz
     - [X] 192000Hz
     (need to figure out what other sample rates ALAC supports)
**** DONE Update source to more closely match documentation
**** DONE Optimize for speed
*** DONE Build ALAC encoder
**** DONE Add audiotools.encoders.encode_alac function
**** DONE Ensure verbatim ALAC file is written correctly
**** DONE Determine ALAC stream tunables
***** wasted bits for 24bps streams
***** interlacing shift
***** interlacing leftweight
***** prediction quantitization
***** coefficients
**** DONE extract wasted bits for 24bps streams
**** DONE correlate stereo samples
**** DONE determine coefficients/quantitization
**** DONE calculate residual values
**** DONE write residuals based on initial history/history multiplier/etc.
**** DONE handle random noise with uncompressed frames
**** DONE handle atypical sample rates properly
**** DONE Update source to more closely match documentation
**** DONE make interlacing shift and interlacing leftweight range options
**** DONE optimize for speed
*** DONE Add ALACAudio type to audiotools Python core
*** DONE Enable TestAlacAudio core tests
*** DONE Add ALAC-specific unit tests
**** DONE test_streams
**** DONE test_small_files
**** DONE test_full_scale_deflection
**** DONE test_sines
     eliminate DeprecationWarning at construct/core.py:541
     which seems to be caused by 96000Hz input
**** DONE test_wasted_bps
**** DONE test_blocksizes
**** DONE test_frame_header_variations
**** DONE test_noise
**** DONE test_fractional
*** DONE Document ALAC
**** DONE the ALAC file stream
**** DONE ALAC decoding
***** DONE Frame header
***** DONE Subframe header
***** DONE Residual decoding
***** DONE Residual decoding example
***** DONE Subframe decoding
      Simplify this to make it easier to understand
***** DONE Subframe decoding example
***** DONE Channel decorrelation
***** DONE Channel decorrelation example
***** DONE Wasted bits
***** DONE Wasted bits example
**** DONE ALAC encoding
***** DONE Figure out compliant atom contents
      - [X] ftyp
      - [X] moov->mvhd
      - [X] moov->trak->tkhd
      - [X] moov->trak->mdia->mdhd
      - [X] moov->trak->mdia->hdlr
      - [X] moov->trak->mdia->minf->smhd
      - [X] moov->trak->mdia->dinf->dref
      - [X] moov->trak->mdia->stbl->stsd->alac
      - [X] moov->trak->mdia->stbl->stts
      - [X] moov->trak->mdia->stbl->stsc
      - [X] moov->trak->mdia->stbl->stsz
      - [X] moov->trak->mdia->stbl->stco
      - [X] moov->udta->meta
      - [X] free
***** DONE Add forward references in "alac" atom description
***** DONE figure out if "meta" has required "----" sub-atoms
***** DONE extracting wasted bits for 24bps streams
***** DONE correlating stereo samples
***** DONE determining coefficients/quantitization
****** DONE Windowing
****** DONE Computing autocorrelation
****** DONE LP coefficient calculation
****** DONE Best order estimation
****** DONE Quantizing coefficients
***** DONE calculating residual values
      Simplify this to make it easier to understand
***** DONE writing residuals based on initial history/history multiplier/etc.
***** DONE Fix bitstream figs to be monospace font for binary digits
**** DONE verify A4 layout is correct
*** DONE Update M4A metadata routines to exploit "free" atoms
    As with FLAC, rewriting the entire file should be avoided
*** DONE Fix InvalidImage exceptions when reading test ALACs
*** DONE Ensure UnsupportedChannels exception is handled by user-level tools
*** DONE Ensure new ALACs work in iTunes
*** DONE Ensure new ALACs work on iPods
*** DONE Have M4A files group properly on iTunes/iPods
** DONE Remove xdelta requirement
   It's doesn't compile well on some platforms and is only used by tracklint
*** DONE Build trivial binary delta routine
    Since most metadata formats make use of padding,
    we can use a simple XOR over their contents to generate
    a bidirectional patch that's optimized for tracklint's behavior.
    Since the bulk of such a patch should be NULLs,
    we can compress it with zlib/bz2 and achieve
    excellent compression.
** DONE Tweak documentation
*** DONE Update indexes to account for warm-up samples
    The i index on the left and right hand sides must match.
    - [X] FLAC FIXED decoding
    - [X] FLAC LPC decoding
    - [X] FLAC FIXED encoding
    - [X] FLAC LPC encoding
*** DONE Rewrite the FLAC channel assignment section
    Reference the side channel extra bit and add examples.
*** DONE Add FLAC channel assignment encoding documentation
    Show its channel calculations and include an example.
** DONE indicate ReplayGain capabilities/binaries
   audiotools-config(1) should show what ReplayGain binaries are present
   and all audio classes that support it
** DONE update trackcmp to share trackverify's output interface
** DONE Add unary jump tables with a max value
   Since both Apple Lossless *and* WavPack have unary reading
   with a maximum upper limit of read bits, it makes sense
   to build a proper jump table for it.
   The size of our state limits the maximum to 8 bits,
   so larger maximums will be supported at a read_limited_unary() level.

   The trouble is, we need to differentiate between normal exits
   (we've hit the stop bit) and exits that hit the maximum value.
   So, the jump table itself must have a different syntax
   (probably an extra bit for "maximum value reached")
   and read_limited_unary() will likely return -1 in that event.
** DONE Add little-endian bitstream readers
*** DONE integrate read functions into Bitstream struct
*** DONE add bitstream.py jump tables for little-endian reading
    - [X] read_bits_table_le.h
    - [X] read_unary_table_le.h
    - [X] read_limited_unary_table_le.h
    - [X] unread_bit_table_le.h
*** DONE add little-endian Bitstream functions
    - [X] bs_read_bits_le
    - [X] bs_read_bits64_le
    - [X] bs_unread_bit_le
    - [X] bs_read_unary_le
    - [X] bs_read_limited_unary_le
*** DONE have bs_open() attach the proper endian functions
*** DONE update Python BitstreamReader for little-endian operation
    This should function similar to the C one,
    with alignment specified at init-time.
    Or, perhaps replace Python reader with C-based one altogether.
*** DONE add some basic Bitstream reader unit testing
*** DONE allow endianness swapping
** DONE Add little-endian bitstream writers
*** DONE add bitstream.py jump tables for little-endian writing
    - [X] write_bits_table_le.h
    - [X] write_unary_table_le.h
*** DONE Add little-endian Bitstream functions
    - [X] write_bits_actual_le
    - [X] write_signed_bits_actual_le
    - [X] write_bits64_actual_le
    - [X] write_unary_actual_le
    - [X] byte_align_w_actual_le
*** DONE have bs_open() attach the proper endian functions
*** DONE update Python BitstreamWriter for little-endian operation
    Convert to a C type, similar to BitstreamReader
*** DONE add some basic Bitstream writer unit testing
*** DONE allow endianness swapping
** DONE integrate new endianness routines into existing routines
*** DONE Convert Ogg verifier to proper little-endian operation
*** DONE Swap endianness for proper FLAC VORBISCOMMENT writing
*** DONE have bs_set_endianness create instruction in recorder-mode
    So that if we're recording an endianness shift,
    it gets set properly when "played back" to an actual writer.
    In fact, it may be a good idea to attach the set_endianness()
    function to the bitstream writer itself.
** DONE Build proper AlbumMetaDataFile class
   This is a superclass of FreeDB's XMCD and MusicBrainz's XML
   which wraps metadata containers into a consistent interface
   for use by editxmcd and command-line utilities.
*** DONE Convert XMCD to AlbumMetaDataFile subclass
*** DONE Add unit tests for XMCD
*** DONE Convert MusicBrainzReleaseXML to AlbumMetaDataFile subclass
*** DONE Add unit tests for MusicBrainzReleaseXML
*** DONE Update utilities to use new interface
    - [X] tracksplit
    - [X] track2track
    - [X] track2xmcd
    - [X] trackrename
    - [X] tracktag
    - [ ] editxmcd
    - [X] cd2track
    - [X] cd2xmcd
*** DONE Update old unit tests to new interface
*** DONE Fix get_track to return blank artist name if not present
    Don't pull from the class artist name;
    have track_metadata() figure that out as appropriate instead.
**** DONE Fix unit tests for proper behavior
*** DONE Document AlbumMetaDataFile
** DONE Update ALAC to handle multichannel audio
   What David Hammerton's reverse-engineered decoder described as
   a 3-bit frame footer isn't; it's actually a "stop" delimiter
   analagous to WavPack's block header stop bit.
   If it's not 0x7, keep reading frames and combine them
   channel-wise into a single multichannel chunk of output.

   iTunes and iPods still won't be able to handle such files,
   but XLD should be able to.
*** DONE Update ALACDecoder's analyze_frame() method for multichannel
*** DONE Update ALACDecoder's read() method for multichannel
*** DONE Update encode_alac function for multichannel
*** DONE Unit test multichannel ALAC encoding and decoding
*** DONE Update decoding documentation describing multichannel handling
*** DONE Update encoding documentation describing multichannel handling
** DONE Add higher sampling rate support to ReplayGain module
   Extract the higher rates from wvgain.c
   - [X] 8000Hz
   - [X] 11025Hz
   - [X] 12000Hz
   - [X] 16000Hz
   - [X] 18900Hz
   - [X] 22050Hz
   - [X] 24000Hz
   - [X] 32000Hz
   - [X] 37800Hz
   - [X] 44100Hz
   - [X] 48000Hz
   - [X] 56000Hz
   - [X] 64000Hz
   - [X] 88200Hz
   - [X] 96000Hz
   - [X] 112000Hz
   - [X] 128000Hz
   - [X] 144000Hz
   - [X] 176400Hz
   - [X] 192000Hz
*** DONE Unit test sample rates
** DONE Allow audio type defaults to be selectable
*** DONE Update tools to pull -t from config file
    - [X] cd2track
    - [X] record2track
    - [X] track2track
    - [X] trackcat
    - [X] tracksplit
*** DONE Update tools to pull -q from config file
    - [X] cd2track
    - [X] record2track
    - [X] track2track
    - [X] trackcat
    - [X] tracksplit
*** DONE Update audio formats to pull default quality from config file (if any)
    for all calls to from_pcm() and from_wave()
    - [X] aac
    - [X] flac
    - [X] m4a (Nero)
    - [X] m4a (faac)
    - [X] mp2
    - [X] mp3
    - [X] oga
    - [X] ogg
    - [X] spx
    - [X] wv
*** DONE Update audiotools-config to display default type
*** DONE Update audiotools-config to display default qualities
*** DONE Update audiotools-config to select default type
*** DONE Update audiotools-config to select default quality for a type
*** DONE Document new configuration options in man pages
    - [X] audiotools-config
    - [X] cd2track
    - [X] record2track
    - [X] track2track
    - [X] trackcat
    - [X] tracksplit
    - [X] audiotools.cfg
** DONE Allow default verbosity to be selectable
*** DONE Update audiotools-config to display default verbosity
*** DONE Update audiotools-config to select default verbosity
*** DONE Update tools to use default verbosity
    - [X] cd2track
    - [X] cd2xmcd
    - [X] coverdump
    - [X] record2track
    - [X] track2cd
    - [X] track2track
    - [X] track2xmcd
    - [X] trackcmp
    - [X] tracklint
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
*** DONE Document new configuration option in man pages
    - [X] audiotools-config
    - [X] audiotools.cfg
** DONE Shift common decoder/encoder routines to a common/ directory
   - [X] flac_crc.h
   - [X] flac_crc.c
   - [X] misc.h
   - [X] misc.c
   - [X] md5.h
   - [X] md5.c
** DONE Get coverview and editxmcd working on Mac OS X
*** DONE editxmcd
**** DONE Update man page
*** DONE Update coverview for dual PyGTK/Tkinter operation
    It's a simple enough app that it should be able to conditionally do both,
    especially since audiotools does most of the heavy lifting.
    This allows it to look like a proper app under X11
    and work at all everywhere else.

    It should look and function approximately the same on both.
**** DONE Remove glade requirement
     I'm sick of glade, and coverview should be small enough
     that it's not a problem to lay it out internally.
**** DONE Update coverview for PyGTK
**** DONE Update coverview for Tkinter
***** DONE Fixup error messages, if possible
**** DONE Add --gtk/--tkinter switches for conditional launch
     For testing purposes
**** DONE Cleanup conditional classes/helper functions
**** DONE Check for init-time errors
     Both in loading audiofiles and in import problems
     such as Mac OS's 32-bit problem
**** DONE Update man page
** DONE More graceful handling of broken files
   A lot of the track handlers assume that once the start of the file is good,
   the rest of it is following the spec.  This is not always the case.
*** DONE All audio formats need to implement the error specification
    This means that:
    classmethod.is_type() must never error
    __init__() must raise InvalidFile if the filename's contents are invalid
    to_pcm() must return PCMReaderError if the decoder can't be built
    classmethod.from_pcm() must raise EncodingError if it can't encode file
    to_wave() must raise EncodingError if a wave can't be written
    classmethod.from_wave() must raise EncodingError if it can't encode file
    a failed to_wave() mustn't leave half-encoded .wav files behind
    a failed from_wave() mustn't leave half-encoded .wav files behind
    a failed from_pcm() mustn't leave a partially encoded file behind
    PCMReaders may raise IOError and ValueError on read()
    PCMReaders may raise DecodingError on close()
**** DONE AACAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE AiffAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE ALACAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE AuAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE FlacAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE M4AAudio_faac
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
**** DONE M4AAudio_nero
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE MP2Audio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE MP3Audio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE OggFlacAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE ShortenAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE SpeexAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
**** DONE VorbisAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE WaveAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
**** DONE WavPackAudio
     - [X] is_type() doesn't error
     - [X] __init__() raises InvalidFile
     - [X] to_pcm() returns PCMReaderError on decoder error
     - [X] from_pcm() raises EncodingError on encoder error
     - [X] to_wave() raises EncodingError on decoder error
     - [X] from_wave() raises EncodingError on encoder error
     - [X] failed to_wave() deletes partial file
     - [X] failed from_wave() deletes partial file
     - [X] failed from_pcm() deletes partial file
     - [X] PCMReader raises IOError/ValueError on read() if necessary
     - [X] PCMReader raises DecodingError on close() if necessary
*** DONE Check for invalid files at the tool level
    If an invalid file is encountered, display a proper
    user-readable error message explaining what's wrong
    with the file.
**** DONE track2cd
**** DONE track2track
**** DONE trackcat
**** DONE trackcmp
**** DONE trackplay
**** DONE tracksplit
*** DONE Document ValueError/IOError behavior
    Both in docstrings and in the rst documentation.
** DONE Add a convert() method to AudioFile subclasses
   Something like:

   audiofile.convert(target_path,
                     target_class,
                     quality=None)

   so one could perform a call like:

   audiotools.open("infile.flac").convert("outfile.mp3", audiotools.MP3Audio)

   Which would perform the proper to_pcm()/from_pcm()/to_wave()/from_wave()
   calls as necessary and could be overloaded to handle specific
   conversion processes, like AIFF->Shorten with a different set of
   IFF chunks.
*** DONE Deprecate wave-specific methods from base AudioFile class
    These methods might still be present, but only on classes that
    have specific need of them.
    - [X] to_wave()
    - [X] from_wave()
    - [X] supports_foreign_riff_chunks()
    - [X] has_foreign_riff_chunks()
*** DONE Convert classes to proper WaveContainer/AiffContainer subclasses
    - [X] FlacAudio
    - [X] OggFlacAudio (should *not* be a WaveContainer/AiffContainer subclass)
    - [X] WaveAudio
    - [X] AiffAudio
    - [X] WavPackAudio
    - [X] ShortenAudio
*** DONE Update old tests to reflect removed methods
*** DONE Update audio formats with convert() short-circuiting as needed
*** DONE Add unit tests for convert() methods
**** DONE Add unit tests which convert() every type to every other type
**** DONE Add unit tests to ensure foreign chunks are converted
*** DONE Update track2track to exploit convert() method
*** DONE Update documentation
**** DONE Add convert() documentation
**** DONE Remove wave-specific documentation
** DONE Update trackverify for multiprocess support
   I'll need some interprocess communication (probably pipes and select)
   to return the results from child processes for totals calculation.
*** DONE Update man page
*** DONE Document ExecQueue2
** DONE Update trackcmp for multiprocess support
*** DONE Update man page
*** DONE Update unit tests to account for -j flag
** DONE Update encoders for thread nonblocking
*** DONE encode_alac
*** DONE encode_flac
*** DONE encode_shn
*** DONE encode_wavpack
** DONE get trackplay working on Mac OS X
   There must be some way to pipe PCM data to its audio system
   without the need for additional libraries.
** DONE Remove -v option to mv(1) for file renaming
   This can be emulated in software.
** DONE Update audiotools.CDDA class
*** DONE Support reading offsets
    Without the horrible hack of reading the whole disc at once
*** DONE Document audiotools.CDDA class
*** DONE Document PCMReaderWindow class
** DONE Add support for System->cdrom_offset
   This will automatically apply offset samples when reading CDs
   so that rips will have the appropate amount of null samples.
*** DONE Add offset support when ripping
    Automatically apply the configfile's cdrom_offset value to tracks
    during reading.
*** DONE Add offset support when burning?
    cd2track and track2cd should round-trip properly
    If cd2track applies a sample offset when reading, does track2cd
    need to apply that same offset when writing?
    One would presume a drive's read offset and write offset are the same,
    but that may not be correct.
** DONE Add support for more lame encoding options
   Although the numerical presets are recommended,
   one should also be able to use the --preset values
   - [X] medium
   - [X] standard
   - [X] extreme
   - [X] insane
** DONE Remove wavegain for applying ReplayGain to .wav files
   This should be done internally instead.
** DONE Add more verbosity to --quality settings
   For formats with varying quality, the "-q help" option
   should indicate what those settings represent.
   - [X] FlacAudio
   - [X] M4AAudio_Nero
   - [X] MP2Audio
   - [X] MP3Audio
   - [X] OggFlacAudio
   - [X] SpeexAudio
   - [X] VorbisAudio
   - [X] WavPackAudio
*** DONE Update tools to indicate quality settings
    - [X] audiotools-config
    - [X] cd2track
    - [X] dvda2track
    - [X] record2track
    - [X] track2track
    - [X] trackcat
    - [X] tracksplit
** DONE Have coverdump build leading directories as needed
** DONE Add -T / --thumbnail option to tracktag
*** DONE Add option to tracktag man page
** DONE Convert ReplayGainReader to C
   The Python implementation simply uses too many CPU cycles,
   which can cause trackplay to stutter on hi-def files.
*** DONE Add audiotools.replaygain.ReplayGainReader object
**** DONE Add sample_rate attribute
**** DONE Add channels attribute
**** DONE Add channel_mask attribute
**** DONE Add bits_per_sample attribute
**** DONE Add read() method
**** DONE Add close() method
*** DONE Convert references to audiotools.ReplayGainReader
*** DONE Update documentation
** DONE Have the *2xmcd utilities delete .xmcd files if cancelled
   - [X] cd2xmcd
   - [X] dvda2xmcd
   - [X] track2xmcd
*** DONE Fix the *2xmcd utilities to use the proper mode on output files
** DONE Add software-based routines for bitstream reading/writing
   These should be optional, at least, for the bitstream writer especially.
   But if fast enough, they could replace the jump tables entirely.
*** DONE bitstream_w.h
    - [X] write_bits_actual_be
    - [X] write_bits_actual_le
    - [X] write_unary_actual_be
    - [X] write_unary_actual_le
** DONE Add individual tag item removal option to tracktag
   - [X] --remove-name
   - [X] --remove-artist
   - [X] --remove-performer
   - [X] --remove-composer
   - [X] --remove-conductor
   - [X] --remove-album
   - [X] --remove-catalog
   - [X] --remove-number
   - [X] --remove-track-total
   - [X] --remove-album-number
   - [X] --remove-album-total
   - [X] --remove-ISRC
   - [X] --remove-media-type
   - [X] --remove-year
   - [X] --remove-date
   - [X] --remove-copyright
   - [X] --remove-comment
*** DONE Unit test tag item addition/removal
*** DONE Update man page with new options
** DONE Add progess indicator to various utilities
   This will likely require an ExecQueue update which can
   receive progress output from multiple subprocesses
   so that the total progress can be generated.
   - [X] track2track
   - [X] cd2track
   - [X] dvda2track
   - [X] tracksplit
   - [X] trackcat
   - [X] trackcmp
   - [X] trackverify
   - [X] tracktag (for ReplayGain)
*** DONE Update AudioFile.convert() with a progress callback option
*** DONE Update AudioFile.add_replay_gain() with a progress callback option
    - [X] AudioFile
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP3Audio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Update programming documentation with progress callback option
*** DONE Update AudioFile.verify() with a progress callback option
    - [X] AACAudio
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] ShortenAudio
    - [X] SpeexAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Add documentation for progress indicators and infrastructure
** DONE Add tool list and documentation to website
   I should probably find a way to either format the man pages
   to HTML directly (though groff's html output has been unfortunate
   in the past) or find an intermediate format that generates
   both man pages and web pages.
   - [X] audiotools-config
   - [X] audiotools.cfg
   - [X] cd2xmcd
   - [X] cdinfo
   - [X] cdplay
   - [X] cd2track
   - [X] coverdump
   - [X] coverview
   - [X] dvda2track
   - [X] dvda2xmcd
   - [X] dvdainfo
   - [X] editxmcd
   - [X] track2cd
   - [X] track2track
   - [X] track2xmcd
   - [X] trackcat
   - [X] trackcmp
   - [X] trackinfo
   - [X] tracklength
   - [X] tracklint
   - [X] trackplay
   - [X] trackrename
   - [X] tracksplit
   - [X] tracktag
   - [X] trackverify
*** DONE Build reStructuredText output
** DONE Add options for ID3v2/ID3v1 tagging
   Perhaps add config file options and corresponding audiotools-config options.
*** DONE Make ID3v2 version selectable
    Either ID3v2.2/ID3v2.3/ID3v2.4/none
*** DONE Make ID3v1 version selectable
    ID3v1.0/ID3v1.1/none
*** DONE Make ID3v2 track number formatting adjustable
    Allow leading 0s, since the spec doesn't forbid them.
*** DONE Display current ID3 info in audiotools-config
*** DONE Add ID3v2.2 option to audiotools-config
*** DONE Add ID3v1 option to audiotools-config
*** DONE Add ID3 number padding to audiotools-config
*** DONE Document new audiotools-config options in man page
*** DONE Document new audiotools config options on website
** DONE Add C-based FLAC encoder
*** DONE Use VERBATIM subframes when necessary
*** DONE Add significant initial padding blocks
    This will save a lot of time during retagging after FLAC creation
*** DONE Add a variety of unit tests
    - [X] test_stream.sh
    - [X] test_flac.sh
*** DONE Convert i_array size and data types to typedefs
*** DONE Convert f_array size and data types to typedefs
*** DONE Add more comprehensive encoding documentation
*** DONE Add a variety of assert() statements
    As with unit tests, these ensure everything is working during testing
    without a performance penalty at runtime.
*** DONE Handle foreign RIFF chunks
*** DONE Ensure FLACs work on a variety of other decoders
    Although decoding properly on the reference decoder *should* guarantee
    the file works everywhere, the only way to be certain is to test it.
*** DONE Ensure encoder raises the proper exceptions
*** DONE Support Rice2 partitions?
    The reference encoder uses these for more efficient handling of 24-bit audio
    but I'm not sure they're strictly necessary for my more basic encoder.
*** DONE Support wasted-bits-per-sample?
    I don't think I've ever seen these used on actual audio data
    that isn't artificial and hasn't been processed specifically for its use.
    As with Rice2, it's something that may get added later.
*** DONE Handle multi-channel PCM data correctly
    Anything higher than 2 channels needs to set a channel mask
    and the vorbis comment to the proper value.
    I expect this will be a long-term project
    coinciding with re-engineering the to_pcm()/from_pcm() methods.
*** DONE Remove external MD5 dependency
*** DONE Generate SEEKTABLE blocks
** DONE Add UTF-8 to FLAC documentation
*** DONE Explain how to decode a UTF-8 value
*** DONE Explain how to encode a UTF-8 value
** DONE Add C-based FLAC decoder
*** DONE Add a variety of unit tests
*** DONE Handle Rice escape codes
    Not sure how to test these, but they should be handled properly.
*** DONE Ensure decoder raises the proper exceptions
*** DONE Handle empty MD5 sums correctly
    If the MD5SUM is 00000000000000000000000000000000
    it's never a mismatch and should not trigger an error.
**** DONE Update tracklint to populate an empty MD5 sum
**** DONE Add unit tests
***** DONE Ensure an empty MD5 doesn't trigger an error at read time
***** DONE Ensure an empty MD5 doesn't trigger an error at verify time
***** DONE Ensure tracklint populates an empty MD5 correctly
** DONE Add substream support to bitstream reader
   Its function is to allow one to pull pieces out of a larger bitstream
   and process them separately.
   For example, while parsing an Ogg stream's pages with one bitstream,
   the extracted Vorbis packets could be processed with smaller substreams -
   including substreams that span one or more pages.
   It's also potentially helpful for MP3, Ogg FLAC, DVD-A and any other format
   with nontrivial wrappers that need to be parsed seperately
   from the main bitstream.
*** DONE Implement substream reader methods
**** DONE bs_read_bits_s_be
**** DONE bs_read_bits_s_le
**** DONE bs_read_signed_bits_s_be
**** DONE bs_read_signed_bits_s_le
**** DONE bs_read_bits64_s_be
**** DONE bs_read_bits64_s_le
**** DONE bs_skip_bits_s_be
**** DONE bs_skip_bits_s_le
**** DONE bs_read_unary_s_be
**** DONE bs_read_unary_s_le
**** DONE bs_read_limited_unary_s_be
**** DONE bs_read_limited_unary_s_le
**** DONE bs_set_endianness_s_be
**** DONE bs_set_endianness_s_le
**** DONE bs_read_huffman_code_s
**** DONE bs_mark_s
**** DONE bs_rewind_s
**** DONE bs_unmark_s
**** DONE bs_close_stream_s
**** DONE bs_substream_new
**** DONE bs_substream_f_be
**** DONE bs_substream_f_le
**** DONE bs_substream_p_be
**** DONE bs_substream_p_le
**** DONE bs_substream_s_be
**** DONE bs_substream_s_le
**** DONE bs_substream_append_f
**** DONE bs_substream_append_p
**** DONE bs_substream_append_s
*** DONE Add substream and substream_append to stream initializers
**** DONE bs_open
     - [X] substream
     - [X] substream_append
**** DONE bs_open_python
     - [X] substream
     - [X] substream_append
*** DONE Update current endianness setters with substream method
**** DONE bs_set_endianness_f_be
**** DONE bs_set_endianness_f_le
**** DONE bs_set_endianness_p_be
**** DONE bs_set_endianness_p_le
*** DONE Add garbage collection to substream
    It should be possible to create a substream, process it partway,
    then append more data to it without causing any overflow problems.
    That is, data at the beginning of the buffer will be recycled
    at append time if it hasn't been marked for rewinding back to.
*** DONE Add Python interface to substream
**** DONE audiotools.decoders.BitstreamReader.substream
**** DONE audiotools.decoders.BitstreamReader.substream_append
*** TODO Add unit tests to substream
** DONE Check objects for invalid init calls
   If an init fails, the subsequent dealloc call shouldn't segfault Python.
*** DONE audiotools.bitstream
    - [X] BitstreamAccumulator
    - [X] BitstreamReader
    - [X] BitstreamRecorder
    - [X] BitstreamWriter
*** DONE audiotools.cdio
    - [X] CDDA
    - [X] CDImage
*** DONE audiotools.decoders
    - [X] ALACDecoder
    - [X] AOBPCMDecoder
    - [X] FlacDecoder
    - [X] MLPDecoder
    - [X] OggFlacDecoder
    - [X] SHNDecoder
    - [X] Sine_Mono
    - [X] Sine_Simple
    - [X] Sine_Stereo
    - [X] WavPackDecoder
*** DONE audiotools.pcm
    - [X] FrameList
    - [X] FloatFrameList
*** DONE audiotools.resample
    - [X] Resampler
** DONE Update BitstreamWriter to work on Python objects
   That is, anything with a .write() and .close() method,
   similar to how BitstreamReader operates.
** DONE Calculate multi-album ReplayGain concurrently
   For instance, if one is calculating --replay-gain for four seperate
   albums and -j 4 is indicated, calculate each album's gain across
   its own core using the existing ProgressQueue facilities.
*** DONE track2track
*** DONE tracktag
**** DONE update man page with -j option
** DONE Adjust FlacMetaData to store blocks internally in order
   That is, they should output in the same order that they are stored on disk.
*** DONE Add size() method to FLAC blocks
    This returns the size of the block data, not including its 32-bit header
    - [X] Flac_STREAMINFO
    - [X] Flac_VORBISCOMMENT
    - [X] Flac_PICTURE
    - [X] Flac_APPLICATION
    - [X] Flac_SEEKTABLE
    - [X] Flac_CUESHEET
    - [X] Flac_PADDING
*** DONE FlacMetaData
    - [X] __init__
    - [X] __setattr__
    - [X] __getattr__
    - [X] __delattr__
    - [X] converted
    - [X] merge
    - [X] add_image
    - [X] delete_image
    - [X] images
    - [X] clean
    - [X] __repr__
    - [X] parse
    - [X] raw_info
    - [X] blocks
    - [X] build
*** DONE FlacAudio
    - [X] channel_mask
    - [X] get_metadata
    - [X] update_metadata
    - [X] set_metadata
    - [X] set_cuesheet
    - [X] get_cuesheet
    - [X] has_foreign_riff_chunks
    - [X] riff_wave_chunks
    - [X] from_wave
    - [X] has_foreign_aiff_chunks
    - [X] from_aiff
    - [X] aiff_chunks
    - [X] add_replay_gain
    - [X] replay_gain
    - [X] clean
*** DONE OggFlacMetaData
    - [X] converted
    - [X] __repr__
    - [X] parse
    - [X] build
*** DONE OggFlacAudio
    - [X] channel_mask
    - [X] update_metadata
    - [X] replay_gain
** DONE Have tracklint add channel mask info to hi-def FLAC files
   If omitted.  Fix cases in which the tag is missing
   from the VORBISCOMMENT block, or the VORBISCOMMENT block
   is absent altogether.
*** DONE Add unit test
*** DONE Add fix to man page
** DONE Update FLAC/Vorbis to support TOTALTRACKS field for track_total
   This seems to be catching on as a standard.
   The best solution may be to implement a set of "fallback" fields
   such that a single metadata field may use a number of
   different Vorbis comment names and the first match is used.
*** DONE Preserve TOTALTRACKS when updating track_total
*** DONE Add unit tests to ensure TOTALTRACKS works
** DONE Don't port ReplayGain tags with AudioFile.set_metadata()
   That is, if we perform:

   >>> track1 = audiotools.open("file1")
   >>> track2 = audiotools.open("file2")
   >>> track1.set_metadata(track2.get_metadata())
   >>> track1.replay_gain() != track2.replay_gain()
   True

   For all audio formats that store ReplayGain as embedded tags
*** DONE Update formats
    - [X] FlacAudio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
*** DONE Add unit tests
    - [X] FlacAudio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
** DONE Have MetaData.converted() always return a new object
   The following should hold for all metadata objects:

   >>> a = MetaData(track_name=u"Foo")
   >>> b = MetaData.converted(a)
   >>> b.track_name = u"Bar"
   >>> a.track_name != b.track_name
   True
*** DONE Update MetaData classes
**** DONE ApeTag
**** DONE FlacMetaData
**** DONE ID3CommentPair
**** DONE ID3v1Comment
**** DONE ID3v22Comment
     - [X] ID3v22_Frame
     - [X] ID3v22_TXX_Frame
     - [X] ID3v22_COM_Frame
     - [X] ID3v22_PIC_Frame
**** DONE ID3v23Comment
     - [X] ID3v23_Frame
     - [X] ID3v23_TXXX_Frame
     - [X] ID3v23_COMM_Frame
     - [X] ID3v23_APIC_Frame
**** DONE ID3v24Comment
     - [X] ID3v24_Frame
     - [X] ID3v24_TXXX_Frame
     - [X] ID3v24_COMM_Frame
     - [X] ID3v24_APIC_Frame
**** DONE M4A_META_Atom
     - [X] M4A_Tree_Atom
     - [X] M4A_Leaf_Atom
     - [X] M4A_META_Atom
     - [X] M4A_FREE_Atom
     - [X] M4A_HDLR_Atom
     - [X] M4A_ILST_Leaf_Atom
     - [X] M4A_ILST_COVR_Data_Atom
     - [X] M4A_ILST_DISK_Data_Atom
     - [X] M4A_ILST_TRKN_Data_Atom
     - [ ] M4A_ILST_Unicode_Data_Atom
**** DONE MetaData
**** DONE OggFlacMetaData
**** DONE VorbisComment
*** DONE Update documentation for converted()'s behavior
*** DONE Add unit tests to all MetaData classes
    - [X] ApeTag
    - [X] FlacMetaData
    - [X] ID3CommentPair
    - [X] ID3v1Comment
    - [X] ID3v22Comment
    - [X] ID3v23Comment
    - [X] ID3v24Comment
    - [X] M4A_META_Atom
    - [X] MetaData
    - [X] OggFlacMetaData
    - [X] VorbisComment
** DONE Add better ID3v2 documentation
   Do a full explanation for frames one will find in nature.
   Don't be too concerned about obscure frames no one ever uses.
   | Frame                       | ID3v2.2 | ID3v2.3 | ID3v2.4 |
   |-----------------------------+---------+---------+---------|
   | all text                    | T__     | T___    | T___    |
   | all web                     | W__     | W___    | W___    |
   | picture                     | PIC     | APIC    | APIC    |
   | comment                     | COM     | COMM    | COMM    |
   | general encapsulated object | GEO     | GEOB    | GEOB    |
   | unsynchronized lyrics       | ULT     | USLT    | USLT    |
*** DONE ID3v2.2
    - [X] COM
    - [X] GEO
    - [X] PIC
    - [X] TAL
    - [X] TBP
    - [X] TCM
    - [X] TCO
    - [X] TCR
    - [X] TDA
    - [X] TDY
    - [X] TEN
    - [X] TFT
    - [X] TIM
    - [X] TKE
    - [X] TLA
    - [X] TLE
    - [X] TMT
    - [X] TOA
    - [X] TOF
    - [X] TOL
    - [X] TOR
    - [X] TOT
    - [X] TP1
    - [X] TP2
    - [X] TP3
    - [X] TP4
    - [X] TPA
    - [X] TPB
    - [X] TRC
    - [X] TRD
    - [X] TRK
    - [X] TSI
    - [X] TSS
    - [X] TT1
    - [X] TT2
    - [X] TT3
    - [X] TXT
    - [X] TXX
    - [X] TYE
    - [X] ULT
    - [X] WAF
    - [X] WAR
    - [X] WAS
    - [X] WCM
    - [X] WCP
    - [X] WPB
    - [X] WXX
*** DONE ID3v2.3
    - [X] APIC
    - [X] COMM
    - [X] GEOB
    - [X] TALB
    - [X] TBPM
    - [X] TCOM
    - [X] TCON
    - [X] TCOP
    - [X] TDAT
    - [X] TDLY
    - [X] TENC
    - [X] TEXT
    - [X] TFLT
    - [X] TIME
    - [X] TIT1
    - [X] TIT2
    - [X] TIT3
    - [X] TKEY
    - [X] TLAN
    - [X] TLEN
    - [X] TMED
    - [X] TOAL
    - [X] TOFN
    - [X] TOLY
    - [X] TOPE
    - [X] TORY
    - [X] TOWN
    - [X] TPE1
    - [X] TPE2
    - [X] TPE3
    - [X] TPE4
    - [X] TPOS
    - [X] TPUB
    - [X] TRCK
    - [X] TRDA
    - [X] TRSN
    - [X] TRSO
    - [X] TSIZ
    - [X] TSRC
    - [X] TSSE
    - [X] TXXX
    - [X] TYER
    - [X] USLT
    - [X] WCOM
    - [X] WCOP
    - [X] WOAF
    - [X] WOAR
    - [X] WOAS
    - [X] WORS
    - [X] WPAY
    - [X] WPUB
    - [X] WXXX
*** DONE ID3v2.4
    - [X] APIC
    - [X] COMM
    - [X] GEOB
    - [X] TALB
    - [X] TBPM
    - [X] TCOM
    - [X] TCON
    - [X] TCOP
    - [X] TDEN
    - [X] TDLY
    - [X] TDOR
    - [X] TDRC
    - [X] TDRL
    - [X] TDTG
    - [X] TENC
    - [X] TEXT
    - [X] TFLT
    - [X] TIPL
    - [X] TIT1
    - [X] TIT2
    - [X] TIT3
    - [X] TKEY
    - [X] TLAN
    - [X] TLEN
    - [X] TMCL
    - [X] TMED
    - [X] TMOO
    - [X] TOAL
    - [X] TOFN
    - [X] TOLY
    - [X] TOPE
    - [X] TOWN
    - [X] TPE1
    - [X] TPE2
    - [X] TPE3
    - [X] TPE4
    - [X] TPOS
    - [X] TPRO
    - [X] TPUB
    - [X] TRCK
    - [X] TRSN
    - [X] TRSO
    - [X] TSOA
    - [X] TSOP
    - [X] TSOT
    - [X] TSRC
    - [X] TSSE
    - [X] TSST
    - [X] TXXX
    - [X] USLT
    - [X] WCOM
    - [X] WCOP
    - [X] WOAF
    - [X] WOAR
    - [X] WOAS
    - [X] WORS
    - [X] WPAY
    - [X] WPUB
    - [X] WXXX
** DONE Adjust ID3v2 to store frames internally in order
   That is, they should output in the same order that they are stored on disk.
*** DONE Rebuild and simplify the ID3v2 frame handling
    Create a simple "frame protocol" which the frames can implement
    and the ID3v2 formats can use so that they parse/build consistently.
**** DONE is_latin_1
**** DONE UCS2Codec
**** DONE decode_syncsafe32
**** DONE encode_syncsafe32
**** DONE __padded_number_pair__
**** DONE __unpadded_number_pair__
**** DONE __number_pair__
**** DONE decode_ascii_c_string
**** DONE encode_ascii_c_string
**** DONE read_id3v2_comment()
     convert to standalone function
**** DONE skip_id3v2_comment()
     convert to standalone function
**** DONE ID3v22Comment
     - [X] __repr__
     - [X] parse
     - [X] build
     - [X] size
     - [X] __len__
     - [X] __getitem__
     - [X] __setitem__
     - [X] __delitem__
     - [X] keys
     - [X] values
     - [X] items
     - [X] __getattr__
     - [X] __setattr__
     - [X] __delattr__
     - [X] raw_info
     - [X] add_image
     - [X] delete_image
     - [X] images
     - [X] converted
     - [X] clean
***** DONE ID3v22_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
      - [X] raw_info
***** DONE ID3v22_TXX_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __unicode__
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] number
      - [X] total
      - [X] raw_info
      - [X] clean
***** DONE ID3v22_PIC_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __getattr__
      - [X] __setattr__
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] raw_info
      - [X] clean
***** DONE ID3v22_COM_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __unicode__
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] raw_info
      - [X] clean
**** DONE ID3v23Comment
     - [X] __repr__
     - [X] parse
     - [X] build
     - [X] size
     - [X] __len__
     - [X] __getitem__
     - [X] __setitem__
     - [X] __delitem__
     - [X] keys
     - [X] values
     - [X] items
     - [X] __setattr__
     - [X] __getattr__
     - [X] __delattr__
     - [X] raw_info
     - [X] add_image
     - [X] delete_image
     - [X] images
     - [X] converted
     - [X] clean
***** DONE ID3v23_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
***** DONE ID3v23_TXXX_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __unicode__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
      - [X] number
      - [X] total
***** DONE ID3v23_APIC_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __getattr__
      - [X] __setattr__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
***** DONE ID3v23_COMM_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __unicode__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
**** DONE ID3v24Comment
     - [X] __repr__
     - [X] parse
     - [X] build
     - [X] size
     - [X] __len__
     - [X] __getitem__
     - [X] __setitem__
     - [X] __delitem__
     - [X] keys
     - [X] values
     - [X] items
     - [X] __setattr__
     - [X] __getattr__
     - [X] __delattr__
     - [X] raw_info
     - [X] add_image
     - [X] delete_image
     - [X] images
     - [X] converted
     - [X] clean
***** DONE ID3v24_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
***** DONE ID3v24_TXXX_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __unicode__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
      - [X] number
      - [X] total
***** DONE ID3v24_APIC_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __getattr__
      - [X] __setattr__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
***** DONE ID3v24_COMM_Frame
      - [X] __init__
      - [X] __repr__
      - [X] __eq__
      - [X] __unicode__
      - [X] raw_info
      - [X] parse
      - [X] build
      - [X] size
      - [X] converted
      - [X] clean
*** DONE Ensure they pass unit tests
*** DONE Ensure UCS-2 C strings are handled correctly
    For UCS-2/UTF-16 encoding, the string should end on 2 NULL bytes
    - [X] ID3v22_COM_Frame
    - [X] ID3v22_PIC_Frame
    - [X] ID3v23_COMM_Frame
    - [X] ID3v23_APIC_Frame
    - [X] ID3v24_COMM_Frame
    - [X] ID3v24_APIC_Frame
*** DONE Handle user-defined text frames properly
    - [X] ID3v22_TXX_Frame
    - [X] ID3v23_TXXX_Frame
    - [X] ID3v24_TXXX_Frame
*** DONE Handle web link frames properly
    - [X] ID3v22_W__Frame
    - [X] ID3v23_W___Frame
    - [X] ID3v24_W___Frame
*** DONE Handle user-defined web link frames properly
    - [X] ID3v22_WXX_Frame
    - [X] ID3v23_WXXX_Frame
    - [X] ID3v24_WXXX_Frame
*** DONE Handle text frames with embedded NULLs
    According to the spec, anything after the first decoded NULL
    should be ignored and not displayed.
    - [X] ID3v22_T__Frame
    - [X] ID3v22_TXX_Frame
    - [X] ID3v23_T___Frame
    - [X] ID3v23_TXXX_Frame
    - [X] ID3v24_T___Frame
    - [X] ID3v24_TXXX_Frame
*** DONE Update tests to always check required/prohibited leading zeroes
    - [X] ID3v22Comment
    - [X] ID3v23Comment
    - [X] ID3v24Comment
** DONE Eliminate the MetaData.merge() method
   The idea of this method is to provide some sort of precedence
   which combining metadata from two sources.
   For example, in track2track, we want provided XMCD metadata
   to override metadata inside the track itself.
   The .merge() method takes a raw MetaData object from XMCD
   and merges it with non-blank fields from the original file
   which means the XMCD metadata is the "base" of sorts.

   A cleaner approach would be to take the raw MetaData object from XMCD,
   get a list of its non-empty fields and call __setattr__ on
   the track's metadata to update those fields.

   MetaData with the highest priority would call __setattr__ last.
*** DONE Remove method from MetaData and subclasses
    - [X] ApeTag
    - [X] FlacMetaData
    - [X] ID3v22Comment
    - [X] ID3v23Comment
    - [X] ID3v24Comment
    - [X] ID3CommentPair
    - [X] M4A_META_Atom
    - [X] MetaData
    - [X] VorbisComment
*** DONE Remove method from tools
    - [X] track2track
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
*** DONE Remove method from documentation
*** DONE Remove method from tests
** DONE Have trackinfo display metadata fields by default
   Instead of displaying the low-level info,
   show fields like "track name", "album name", etc.
   unless a -R/--raw flag is indicated.
*** DONE Replace MetaData.comment_pairs() with MetaData.raw_info()
    This should be even lower level than it is now, and unsorted.
    - [X] ApeTag
    - [X] FlacMetaData
    - [X] ID3CommentPair
    - [X] ID3v1Comment
    - [X] ID3v22Comment
    - [X] ID3v23Comment
    - [X] ID3v24Comment
    - [X] M4A_META_Atom
    - [X] OggFlacMetaData
    - [X] VorbisComment
**** DONE Document MetaData.raw_info() method
*** DONE Convert MetaData.__unicode__() to display fields by name
    This should always use the higher level implementation
    and sort fields by name.
*** DONE Update trackinfo to use MetaData's unicode output by default
*** DONE Add -L/--low-level option to trackinfo
*** DONE Add -L/--low-level option to trackinfo.1
*** DONE Add consistent embedded cuesheet display
*** DONE Update unit tests
** DONE Make metadata fully round-trippable
   For instance, the following should hold:

   >>> track_data1 = open(track.filename).read()
   >>> track.update_metadata(track.get_metadata())
   >>> track_data2 = open(track.filename).read()
   >>> track_data1 == track_data2
   True

   This requires metadata to not jumble fields around until required.
** DONE Don't populate empty track_name, ISRC values during tracksplit
** DONE Fix ALAC channel assignment
   Since the channel assignment is detailed in the specs,
   add proper support for them.
*** DONE Update ALACAudio.channel_mask() method
*** DONE Update ALACAudio.from_pcm() method
    Like FLAC, it should reject unsupported channel masks.
*** DONE Update py_decoders.ALACDecoder's .channel_mask attribute
*** DONE Update py_decoders.ALACDecoder's .read() method
    Convert ALAC channel order to Wave channel order internally
    rather than punt that task to a reordering wrapper.
*** DONE Update py_encoders.encode_mdat
    Convert Wave channel order to ALAC channel order internally
    rather than punt that task to a reordering wrapper.
*** DONE Update decoders.ALACDecoder's .channel_mask attribute
*** DONE Update decoders.ALACDecoder's .read() method
    Convert ALAC channel order to Wave channel order internally
    rather than punt that task to a reordering wrapper.
*** DONE Update encoders.encode_alac
    Convert Wave channel order to ALAC channel order internally
    rather than punt that task to a reordering wrapper.
*** DONE Add unit tests to ensure channel variants encode/decode correctly
    - [X] 1 channel
    - [X] 2 channels
    - [X] 3 channels
    - [X] 4 channels
    - [X] 5 channels
    - [X] 6 channels
    - [X] 7 channels
    - [X] 8 channels
**** DONE Add unit tests to ensure invalid channel counts aren't encoded
**** DONE Add unit tests to ensure invalid channel masks aren't encoded
** DONE Add Python-based file encoders
   Like the Python-based decoders,
   these provide simple reference implementations
   one can easily pull apart to see how the encoders work.
*** DONE py_encoders/encode_flac
*** DONE py_encoders/encode_shn
*** DONE py_encoders/encode_alac
*** DONE py_encoders/encode_wavpack
** DONE Cleanup Shorten for better accuracy
*** DONE Handle 8-bit files correctly
    waves are unsigned, aiffs are signed, Sun AU is ?
    - [X] update py_decoders.SHNDecoder
    - [X] update decoders.SHNDecoder
    - [X] update py_encoders.encode_shn
    - [X] update encoders.encode_shn
    - [X] update __shn__.py
    - [X] update documentation
*** DONE Handle multichannel Shn/AIFF files correctly
**** DONE Ensure multichannel files converted from AIFF work
     Their channels should be kept in whatever order AIFF happens to use
     rather than have them remapped to wave order.
**** DONE Ensure multichannel Shn/AIFF to PCM works
     Output channels should be rearranged to PCMReader order.
*** DONE Improve thread friendliness in decoder/encoder
    - [X] decoders/shn.c
    - [X] encoders/shn.h
** DONE Overhaul array.h
   Just as the bitstream module benefits from having a good design,
   the array module would also be better served by having
   a more elegant API.

   The main goal is to have less function variants and
   data allocation/deallocation routines to remember.
   Something like:

   array = new_int_array();
   array->append(array, value);
   array->del(array);

   which always functions the same whether we're dealing with ints or
   floats or other arrays, yet is still type-checked at compile-time.
*** DONE Better unify FrameList <-> array_ia routines
*** DONE Remove old src/decoders/pcm.h src/decoders/pcm.c module
    Use the unified pcmconv.h module instead
    - [X] mlp.c
    - [X] shn.c
    - [X] sine.c
*** DONE Replace old array.h with new one in all source files
    - [X] decoders/mlp.h
    - [X] decoders/shn.h
    - [X] decoders/sine.h
    - [X] decoders/wavpack.h
    - [X] encoders/alac.h
    - [X] encoders/shn.h
    - [X] encoders/wavpack.h
*** DONE Remove old array.h
*** DONE Replace array2.h with array.h in all source files
** DONE Make second pass through updated documentation
*** DONE Update introduction
**** DONE re-explain endianness
**** DONE explain pseudocode with actual code examples
*** DONE Convert writes to a systax consistent with reads
    var <- read 10 unsigned bits
    var -> write 10 unsigned bits
    - [X] alac.tex
    - [X] flac.tex
    - [X] shorten.tex
    - [X] wavpack.tex
*** DONE Convert for loops to a consistent assignment syntax
    for i <- 0 to x do
       <code block>
    - [X] alac.tex
    - [X] dvda2.tex
    - [X] flac.tex
    - [X] shorten.tex
    - [X] wavpack.tex
*** DONE Double-check codecs for consistency
    - [X] alac.tex
    - [X] dvda2.tex
    - [X] flac.tex
    - [X] shorten.tex
    - [X] wavpack.tex
*** DONE Add hyperref linking
    If one's browsing the PDF,
    one should be able to click on operations directly
    and go to a specific part of the doc.
    - [X] alac.tex
    - [X] dvda2.tex
    - [X] flac.tex
    - [X] shorten.tex
    - [X] wavpack.tex
** DONE Tweak track labeling interactive mode for better usability
   make return key move to the next line
** DONE Combine and simplify DVD-A decoding and documentation
   The current routine bounces back and forth between Python and C
   several times in order to generate output, overcomplicating the design.
   I'd prefer to have a simpler, low-level, random-access reader hooked directly
   to the DVDATrack object - analagous to audiotools.cdda.CDDA.
** DONE Overhaul decoding/encoding documentation
   It needs to be cleaned up so one can better follow the entire
   decoding process, as well as the entire encoding process.
   Use a mix of pseudocode, bit diagrams, bit parsing diagrams
   and examples so that it can be followed with as little effort as possible.
*** DONE FLAC
**** DONE decoding
**** DONE encoding
*** DONE ALAC
**** DONE decoding
**** DONE encoding
*** DONE WavPack
**** DONE decoding
**** DONE encoding
*** DONE Shorten
**** DONE decoding
**** DONE encoding
*** DONE DVD-A
**** DONE decoding
* DONE Finish version 2.18
** DONE Ensure audiotools works on FreeBSD
   the unprotection module, in particular, needs additional testing
** DONE Ensure excessive zero residuals don't overflow output buffers
   Certain formats provide "escape code" blocks of zeroes.
   Ensure these routines don't generate more zeroes than are allowed
   (either accidentally or deliberately).
*** DONE ALACDecoder
*** DONE WavPackDecoder
** DONE Cleaup documentation layout
   Add subdirectories for format figures.
** DONE Spellcheck reference docs
   - [X] introduction.tex
   - [X] basics.tex
   - [X] wav.tex
   - [X] aiff.tex
   - [X] au.tex
   - [X] shorten.tex
   - [X] flac.tex
   - [X] wavpack.tex
   - [X] ape.tex
   - [X] mp3.tex
   - [X] m4a.tex
   - [X] alac.tex
   - [X] vorbis.tex
   - [X] oggflac.tex
   - [X] speex.tex
   - [X] musepack.tex
   - [X] dvda2.tex
   - [X] freedb.tex
   - [X] musicbrainz.tex
   - [X] replaygain.tex
** DONE Make ReplayGain a configurable option
   Even tag-based ReplayGain should be something
   users can turn on or off globally via audiotools-config
*** DONE add audiotools-config option
**** DONE document in audiotools-config man page
*** DONE update tools to use option
    - [X] cdtrack
    - [X] dvda2track
    - [X] track2track
    - [X] tracksplit
** DONE Add progress to track2cd audio file conversion
   - [X] CD quality, embedded cuesheet
   - [X] non-CD quality, embedded cuesheet
   - [X] CD quality, external cuesheet
   - [X] non-CD quality, external cuesheet
   - [X] CD quality, multiple files
   - [X] non-CD quality, multiple files
** DONE Improve .wav performance
   don't read entire data chunk by default
** DONE Integrate MusicBrainz/FreeDB lookup with cd2track/dvda2track
   The two step extraction process is a relic from when I'd do
   batch lookups via modem.
   Combining metadata querying with extraction lets me perform more
   powerful lookups than are possible by using XMCD/XML file intermediaries.

   However, CD lookups may be still be wrong.
   Therefore, it's essential to have a simple, interactive
   track metadata editor so that this data is very easy to populate.
*** DONE Add multi-track interactive editing mode to tracktag
**** DONE Update man page
*** DONE Build unified metadata selection widget for interactive modes
    This is something that can be run while a disc is being extracted
    or a track is being split which will drop the user back
    into a progress indicator once completed and then
    tag/rename the resulting tracks.
*** DONE Add MusicBrainz/FreeDB lookup options to audiotools-config
    It should be possible to decide whether to query either or both
    as a config option.
*** DONE Query MusicBrainz for album_number/album_total info
    Given a particular disc ID, there must be some way to pull
    the disc's album_number/album_total off MusicBrainz's servers
    if it's one of a series of discs.
*** DONE Add pre-extraction metadata lookup to tracksplit
**** DONE Update man page
*** DONE Add pre-extraction metadata lookup to cd2track
**** DONE Update man page with new options
*** DONE Add pre-extraction metadata lookup to dvda2track
**** DONE Update man page with new options
*** DONE Update cdinfo to use new ID calculation routines
    Ensure they handle the FreeDB test disc properly.
*** DONE Add pre-play metadata lookup to cdplay
**** DONE Update man page
*** DONE Add album-number/album-total options to tracksplit
    Allow these values to be populated at split-time
    if none can be found in metadata services
**** DONE Update man page
*** DONE Update album-number/album-total options in cd2track
*** DONE Remove xmcd-specific options
    Automatic CD lookup should be folded into utilites as needed.
    - [X] cd2track
    - [X] cdplay
    - [X] dvda2track
    - [X] track2track
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
*** DONE Remove xmcd-specific tools
    - [X] cd2xmcd
    - [X] dvda2xmcd
    - [X] editxmcd
    - [X] track2xmcd
*** DONE Update unit tests
    - [X] cd2track
    - [X] dvda2track
    - [X] tracksplit
*** DONE Deprecate xmcd-specific modules
    Since metadata lookup handles files internally,
    there's no need for metadata file handling classes/functions
    at the Python level.
**** DONE Update documentation to indicate deprecation
** DONE Adjust album_number/track_number heuristics
   these should be last-resort fields that apply
   *only* if a track has no metadata of any kind
** DONE Don't port cuesheets with AudioFile.set_metadata()
   This is mostly for WavPack since it embeds cuesheet data
   in the APEv2 tag.
   - [X] FlacAudio
   - [X] OggFlacAudio
   - [X] WavPackAudio
** DONE Ensure overly-long files are handled correctly
   That is, anything larger than a .wav can typically handle
*** DONE wave should fail with error
*** DONE aiff should fail with error
*** DONE au should fail with error
*** DONE flac should work
*** DONE ogg flac should work
*** DONE wavpack should work
*** DONE alac should work
*** DONE shorten shouldn't begin
    requires verbatim wave or aiff chunks
    which can't be created because
    the amount of PCM data is too large
** DONE Fix FLAC embedded cuesheets
** DONE Ensure files are PEP8-compliant
*** DONE check user-level scripts
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] record2track
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
    - [X] setup.py
*** DONE check audiotools module
    - [X] __aiff__.py
    - [X] __ape__.py
    - [X] __au__.py
    - [X] __dvda__.py
    - [X] __flac__.py
    - [X] __freedb__.py
    - [X] __id3__.py
    - [X] __id3v1__.py
    - [X] __image__.py
    - [X] __init__.py
    - [X] __m4a__.py
    - [X] __m4a_atoms__.py
    - [X] __mp3__.py
    - [X] __musepack__.py
    - [X] __musicbrainz__.py
    - [X] __ogg__.py
    - [X] __shn__.py
    - [X] __vorbis__.py
    - [X] __vorbiscomment__.py
    - [X] __wav__.py
    - [X] __wavpack__.py
    - [X] cue.py
    - [X] delta.py
    - [X] freedb.py
    - [X] musicbrainz.py
    - [X] player.py
    - [X] toc.py
    - [X] ui.py
*** DONE check audiotools.py_decoders module
    - [X] __init__.py
    - [X] alac.py
    - [X] flac.py
    - [X] shn.py
    - [X] wavpack.py
*** DONE check audiotools.py_encoders module
    - [X] __init__.py
    - [X] alac.py
    - [X] flac.py
    - [X] shn.py
    - [X] wavpack.py
*** DONE check test
    - [X] test.py
    - [X] test_core.py
    - [X] test_formats.py
    - [X] test_metadata.py
    - [X] test_streams.py
    - [X] test_utils.py
** DONE Double-check reference docs one last time
*** DONE Ensure pages break correctly in letter mode
*** DONE Ensure pages break correctly in A4 mode
** DONE Fix FLAC seektable generation
   Ensure new seektables are aligned properly.
*** DONE Ensure proper seektable written on from_pcm
*** DONE Add .offsets() method to FlacDecoder
    Instead of decoding the file, this walks through it
    and returns a list of absolute file offsets of all frames.
*** DONE Add method to generate Flac_SEEKTABLE from offset list
*** DONE Add tracklint check/fix for mis-aligned seektables
*** DONE Add unit tests for seektable errors
    - [X] empty seekpoints
    - [X] mis-ordered seekpoints
    - [X] bad seekpoint destinations
** DONE Shift lint to the AudioFile and MetaData subclasses
   Instead of having tracklint have to perform lots of built-in tests,
   it would be better to have clean() functions added to the classes
   themselves.
   For MetaData, it could return a new object with fixed fields.
   For AudioFile, it could function like convert() and build a new file
   with fixes applied.
*** DONE Add clean() method to MetaData and subclasses
**** DONE MetaData
**** DONE ApeTag
**** DONE WavPackAPEv2
**** DONE FlacMetaData
**** DONE ID3v22Comment
**** DONE ID3v23Comment
**** DONE ID3v24Comment
**** DONE ID3v1Comment
**** DONE ID3CommentPair
**** DONE M4AMetaData
**** DONE VorbisComment
***** DONE FlacVorbisComment
***** DONE UnframedVorbisComment
*** DONE Add clean() method to AudioFile and subclasses
    I expect a lot of these will do nothing in the short term.
**** DONE AudioFile
**** DONE AiffAudio
     - [X] Reorder streams in which the COMM chunk doesn't come before data
     - [X] remove duplicate COMM chunks
     - [X] remove duplicate SSND chunks
***** DONE add unit test for verify()
      - [X] multiple COMM chunks found
      - [X] multiple SSND chunks found
      - [X] SSND chunk before COMM chunk
***** DONE add unit test for clean()
      - [X] multiple COMM chunks found
      - [X] multiple SSND chunks found
      - [X] SSND chunk before COMM chunk
**** DONE FlacAudio
**** DONE WaveAudio
     - [X] reorder streams in which the fmt chunk doesn't come before data
     - [X] remove duplicate fmt chunks
     - [X] remove duplicate data chunks
***** DONE add unit test for verify()
      - [X] multiple fmt chunks found
      - [X] multiple data chunks found
      - [X] data chunk before fmt chunk
***** DONE add unit test for clean()
      - [X] multiple fmt chunks found
      - [X] multiple data chunks found
      - [X] data chunk before fmt chunk
*** DONE Document MetaData clean() method
*** DONE Document AudioFile clean() method
*** DONE Add more comprehensive unit tests for clean() methods
*** DONE Convert tracklint to use clean() methods
** DONE Ensure individual unit tests pass
*** DONE Lib
    - [X] core
    - [X] cuesheet
    - [X] freedb
    - [X] image
    - [X] musicbrainz
    - [X] pcm
    - [X] bitstream
    - [X] replaygain
    - [X] resample
    - [X] tocfile
    - [X] verify
    - [X] player
*** DONE Format
    - [X] audiofile
    - [X] lossless
    - [X] lossy
    - [X] aiff
    - [X] alac
    - [X] au
    - [X] dvda
    - [X] flac
    - [X] m4a
    - [X] mp2
    - [X] mp3
    - [X] oggflac
    - [X] shorten
    - [X] sines
    - [X] vorbis
    - [X] wave
    - [X] wavpack
*** DONE Metadata
    - [X] metadata
    - [X] flac
    - [X] wavpack
    - [X] id3v1
    - [X] id3v2
    - [X] vorbis
    - [X] m4a
*** DONE Util
    - [X] audiotools_config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] record2track
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
** DONE Ensure complete unit test passes
   this includes some compound tests
   which may not be covered by individual tests
** DONE Cleanup bitstream module
   This is at the heart of a lot of things so it should be properly cleaned up.
*** DONE Merge bitstream_r and bitstream_w source files
*** DONE Make BitstreamReader and BitstreamWriter more symmetric
*** DONE Add placeholders to BitstreamWriter
    These allow one to put temporary values in the stream
    which can be automatically filled-in later on.
**** DONE Add write_placeholder method to BitstreamWriter
**** DONE Add fill_placeholder method to BitstreamWriter
**** DONE Add write_64_placeholder method to BitstreamWriter
**** DONE Add fill_64_placeholder method to BitstreamWriter
**** DONE Implement bw_write_placeholder_f
**** DONE Implement bw_write_64_placeholder_f
**** DONE Implement bw_fill_placeholder_f
**** DONE Implement bw_fill_64_placeholder_f
**** DONE Implement bw_write_placeholder_r
**** DONE Implement bw_write_64_placeholder_r
**** DONE Implement bw_fill_placeholder_r
**** DONE Implement bw_fill_64_placeholder_r
**** DONE Implement bw_write_placeholder_a
     Should manage the placeholder stack, but only keep track of bits sent
**** DONE Implement bw_write_64_placeholder_a
     Should manage the placeholder stack, but only keep track of bits sent
**** DONE Implement bw_fill_placeholder_a
     Should manage the placeholder stack, but not do anything else
**** DONE Implement bw_fill_64_placeholder_a
     Should manage the placeholder stack, but not do anything else
**** DONE Store filled placeholders in a stack for reuse
**** DONE Clean out allocated placeholders as close() time
***** DONE Trigger warnings if not all placeholders are used at close()
*** DONE Add C-based unit tests
    This should be something I can compile into a standalone file
    and run with no external dependencies.
    The idea is to build something valgrind-able to ensure
    there's no memory errors.
**** DONE BitstreamReader
***** DONE big-endian
      - [X] read
      - [X] read_signed
      - [X] read_64
      - [X] skip
      - [X] skip_bytes
      - [X] unread
      - [X] read_unary
      - [X] read_limited_unary
      - [X] read_huffman_code
      - [X] byte_align
      - [X] read_bytes
      - [X] set_endianness
      - [X] mark
      - [X] unmark
      - [X] rewind
***** DONE little-endian
      - [X] read
      - [X] read_signed
      - [X] read_64
      - [X] skip
      - [X] skip_bytes
      - [X] unread
      - [X] read_unary
      - [X] read_limited_unary
      - [X] read_huffman_code
      - [X] byte_align
      - [X] read_bytes
      - [X] set_endianness
      - [X] mark
      - [X] unmark
      - [X] rewind
***** DONE br_try/etry
      - [X] read
      - [X] read_signed
      - [X] read_64
      - [X] skip
      - [X] skip_bytes
      - [X] read_unary
      - [X] read_limited_unary
      - [X] read_huffman_code
      - [X] read_bytes
      - [X] substream_append
***** DONE Callbacks
      - [X] read
      - [X] read_signed
      - [X] read_64
      - [X] skip
      - [X] skip_bytes
      - [X] read_unary
      - [X] read_limited_unary
      - [X] read_huffman_code
      - [X] read_bytes
**** DONE Substream BitstreamReader
**** DONE BitstreamWriter
***** DONE big-endian
***** DONE little-endian
***** DONE callbacks
**** DONE BitstreamRecorder
**** DONE BitstreamAccumulator
*** DONE Shift BitstreamReader/BitstreamWriters to audiotools.bitstream
*** DONE Add documentation for audiotools.bitstream
**** DONE BitstreamReader
     - [X] add_callback
     - [X] byte_align
     - [X] call_callbacks
     - [X] close
     - [X] limited_unary
     - [X] mark
     - [X] parse
     - [X] pop_callback
     - [X] read
     - [X] read64
     - [X] read_bytes
     - [X] read_huffman_code
     - [X] read_signed
     - [X] read_signed64
     - [X] rewind
     - [X] set_endianness
     - [X] skip
     - [X] skip_bytes
     - [X] substream
     - [X] substream_append
     - [X] unary
     - [X] unmark
     - [X] unread
**** DONE BitstreamWriter
     - [X] add_callback
     - [X] build
     - [X] byte_align
     - [X] call_callbacks
     - [X] close
     - [X] flush
     - [X] pop_callback
     - [X] set_endianness
     - [X] unary
     - [X] write
     - [X] write64
     - [X] write_bytes
     - [X] write_signed
     - [X] write_signed64
**** DONE BitstreamRecorder
     - [X] add_callback
     - [X] bits
     - [X] build
     - [X] byte_align
     - [X] bytes
     - [X] call_callbacks
     - [X] close
     - [X] copy
     - [X] data
     - [X] flush
     - [X] pop_callback
     - [X] reset
     - [X] set_endianness
     - [X] split
     - [X] swap
     - [X] unary
     - [X] write
     - [X] write64
     - [X] write_bytes
     - [X] write_signed
     - [X] write_signed64
**** DONE BitstreamAccumulator
     - [X] bits
     - [X] build
     - [X] byte_align
     - [X] bytes
     - [X] close
     - [X] reset
     - [X] set_endianness
     - [X] unary
     - [X] write
     - [X] write64
     - [X] write_bytes
     - [X] write_signed
     - [X] write_signed64
**** DONE HuffmanTree
**** DONE Substream
**** DONE format_size
*** DONE Handle bitstream closing more consistently
    The basic handle cycle is:

    h = open_handle(substream); /*allocates space for handle*/
    h->method(h);               /*performs reads/writes on handle*/
    ...
    h->close(h);                /*closes substream and deallocates handle*/

    But we also need two additional methods
    for working with the handle and its substream seperately.

    | method            | substream      | handle      |
    |-------------------+----------------+-------------|
    | close()           | flushed/closed | deallocated |
    | close_substream() | flushed/closed | nothing     |
    | free()            | nothing        | deallocated |
    |-------------------+----------------+-------------|

    This is especially important for the Python wrappers
    in which closing and dealloacting will come from different routines.
**** DONE Add functions to bitstream core
***** DONE BitstreamReader
      - [X] br_close_substream_f
      - [X] br_close_substream_s
      - [X] br_close_substream_p
      - [X] br_close_methods
      - [X] br_free_f
      - [X] br_free_s
      - [X] br_free_p
      - [X] br_close
      - [X] br_read_bits_c
      - [X] br_read_bits64_c
      - [X] br_skip_bits_c
      - [X] br_unread_c
      - [X] br_read_unary_c
      - [X] br_read_limited_unary_c
      - [X] br_read_huffman_code_c
      - [X] br_read_bytes_c
      - [X] br_set_endianness_c
      - [X] br_close_substream_c
      - [X] br_mark_c
      - [X] br_rewind_c
      - [X] br_unmark_c
      - [X] br_substream_append_c
***** DONE BitstreamWriter
      - [X] bw_close_substream_f
      - [X] bw_close_substream_r
      - [X] bw_close_substream_p
      - [X] bw_close_substream_a
      - [X] bw_close_methods
      - [X] bw_free_f_a
      - [X] bw_free_r
      - [X] bw_free_p
      - [X] bw_close
      - [X] bw_write_bits_c
      - [X] bw_write_bits64_c
      - [X] bw_write_bytes_c
      - [X] bw_write_signed_bits_c
      - [X] bw_write_signed_bits64_c
      - [X] bw_write_unary_c
      - [X] bw_set_endianness_c
      - [X] bw_close_substream_c
      - [X] bw_byte_align_c
***** DONE Ensure bw_rec_split detects closed stream(s) properly
      Call bw_abort if one attempts to split from or to a closed stream.
***** DONE Ensure bw_dump_bytes detects closed stream properly
      Call bw_abort if one attempts to dump bytes to a closed stream.
***** DONE Ensure bw_rec_copy detects closed stream properly
      Call bw_abort if one attempts to copy records to a closed stream.
**** DONE Update mod_bitstream.c to use new hooks properly
***** DONE Add bw_try/bw_etry wrappers around write methods
      - [X] BitstreamWriter_write
      - [X] BitstreamWriter_write_signed
      - [X] BitstreamWriter_write64
      - [X] BitstreamWriter_write_signed64
      - [X] BitstreamWriter_unary
      - [X] BitstreamWriter_byte_align
      - [X] BitstreamWriter_write_bytes
      - [X] BitstreamWriter_flush
      - [X] BitstreamRecorder_write
      - [X] BitstreamRecorder_write_signed
      - [X] BitstreamRecorder_write64
      - [X] BitstreamRecorder_write_signed64
      - [X] BitstreamRecorder_unary
      - [X] BitstreamRecorder_byte_align
      - [X] BitstreamRecorder_write_bytes
      - [X] BitstreamRecorder_copy
      - [X] BitstreamRecorder_split
      - [X] bitstream_build
      - [X] BitstreamAccumulator_write
      - [X] BitstreamAccumulator_write_signed
      - [X] BitstreamAccumulator_write64
      - [X] BitstreamAccumulator_write_signed64
      - [X] BitstreamAccumulator_unary
      - [X] BitstreamAccumulator_byte_align
      - [X] BitstreamAccumulator_write_bytes
***** DONE Ensure close() method calls bs->close_substream()
      - [X] BitstreamWriter
      - [X] BitstreamRecorder
      - [X] BitstreamAccumulator
***** DONE Ensure dealloc method calls bs->free()
      - [X] BitstreamWriter
      - [X] BitstreamRecorder
      - [X] BitstreamAccumulator
***** DONE Don't fclose() file objects from underneath Python file objects
      Although we may convert a Python file object to a FILE struct,
      we must not fclose that struct out from underneath its parent.
****** DONE BitstreamReader_close
       - [X] call .close() method on Python file object
       - [X] set read methods to raise errors
       - [X] return result of .close() method
****** DONE BitstreamWriter_close
       - [X] flush pending output
       - [X] call .close() method on Python file object
       - [X] set write methods to raise errors
       - [X] return result of .close() method
****** DONE BitstreamRecorder_close
****** DONE BitstreamAccumulator_close
**** DONE Double-check existing C modules for proper free/close usage
     - [X] bitstream.c
     - [X] decoders/alac.c
     - [X] decoders/flac.c
     - [X] decoders/mlp.c
     - [X] decoders/ogg.c
     - [X] decoders/shn.c
     - [X] decoders/wavpack.c
     - [X] encoders/alac.c
     - [X] encoders/flac.c
     - [X] encoders/flac_lpc.c
     - [X] encoders/shn.c
     - [X] encoders/wavpack.c
     - [X] mod_bitstream.c
     - [X] verify/mpeg.c
     - [X] verify/ogg.c
*** DONE Ensure that reading/writing closed streams always fails
    Once a stream's been closed,
    the bitstream should set I/O functions to error generators
    and further closes should do nothing.
**** DONE Add unit test to bitstream.c
**** DONE Add unit test to Python side
** DONE Add update_metadata() method to AudioFile
   Consider how AudioFile.set_metadata() handles six different cases:

   Case 1: adjusting a file's own textual metadata (tracktag)
   >>> m = flac1.get_metadata()
   >>> m.track_name = u"Fixed Name"
   >>> flac1.set_metadata(m)

   Case 2: transferring metadata from the same audio type (track2track)
   >>> flac1.set_metadata(flac2.get_metadata())

   Case 3: transferring metadata from a different audio type (track2track)
   >>> flac1.set_metadata(mp3.get_metadata())

   Case 4: adjusting a file's own low-level metadata (tracklint)
   >>> m = flac1.get_metadata()
   >>> m.streaminfo.md5sum = fixed_md5sum
   >>> flac1.set_metadata(m)

   Case 5: building new metadata from scratch (cd2track)
   >>> flac1 = FlacAudio.from_pcm(pcm_reader)
   >>> flac1.set_metadata(MetaData(track_name=u"New Name", ...))

   Case 6: transferring adjusted metadata from the same audio type
   >>> m = flac2.get_metadata()
   >>> m.track_name = u"Adjusted Name"
   >>> flac1.set_metadata(m)

   What happens to stuff like the STREAMINFO block
   which contains track length, md5sum, etc.
   and is part of FLAC's metadata?

   If set_metadata() works naively and doesn't override any values,
   case 2 breaks since flac1 suddenly has flac2's track length.

   If set_metadata() overrides new metadata with stuff from the current file,
   case 4 breaks since the fixed_md5sum gets overridden.

   Counting on set_metadata() being smart enough to know what you mean
   is a losing proposition for FLAC, M4A and other metadata formats
   with embedded non-textual (side) metadata.

   One solution is to add a low-level AudioFile.update_metadata() method
   which takes only the AudioFile's required metadata type
   and leaves side data as-is.  So case 1 becomes:

   >>> m = flac1.get_metadata()
   >>> m.track_name = u"Fixed Name"
   >>> flac1.update_metadata(m)

   and case 4 becomes:

   >>> m = flac1.get_metadata()
   >>> m.streaminfo.md5sum = fixed_md5sum
   >>> flac1.update_metadata(m)

   while the other cases remain unchanged.
*** DONE Add update_metadata() method to AudioFile and subclasses
    for formats which take metadata at all
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AudioFile
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
*** DONE Update clean() method to use update_metadata()
    for formats which implement clean() at all
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
*** DONE Add update_metadata() to tracktag
    when not using -r/--replace option
*** DONE Document new set_metadata() behavior
*** DONE Document update_metadata() method
*** DONE Add unit tests for update_metadata()
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
** DONE Ensure Python API docs are updated if necessary
   - [X] audiotools.rst
   - [X] audiotools_bitstream.rst
   - [X] audiotools_cdio.rst
   - [X] audiotools_cue.rst
   - [X] audiotools_pcm.rst
   - [X] audiotools_player.rst
   - [X] audiotools_replaygain.rst
   - [X] audiotools_resample.rst
   - [X] audiotools_toc.rst
   - [X] index.rst
   - [X] metadata.rst
** DONE Spellcheck Python API docs
   - [X] audiotools.rst
   - [X] audiotools_bitstream.rst
   - [X] audiotools_cdio.rst
   - [X] audiotools_cue.rst
   - [X] audiotools_pcm.rst
   - [X] audiotools_player.rst
   - [X] audiotools_replaygain.rst
   - [X] audiotools_resample.rst
   - [X] audiotools_toc.rst
   - [X] index.rst
   - [X] metadata.rst
** DONE Eliminate all compiler warnings
   - [X] check Ubuntu
   - [X] check Fedora
   - [X] check Mac OS X
** DONE Reduce load from progress updating routines
   Send fewer progress updates from child to parent
   when performing progress-based tasks.

   This is harder than it sounds.

   In an ideal world, I'd have the parent poll its children
   on a regular basis and update the progress bars
   and/or start more children.

   In practice, this doesn't work with subprocesses and shared memory
   because shared memory accumulators don't free themselves correctly
   when working with more than one child process.

   It works even less well with threading because subprocess.Popen
   isn't thread-safe and will certainly deadlock if called often enough.

   So although the current system is clunky and inefficient
   at least it works.
** DONE Ensure all Python modules have up-to-date docstrings
   - [X] __aiff__.py
   - [X] __ape__.py
   - [X] __au__.py
   - [X] __dvda__.py
   - [X] __flac__.py
   - [X] __id3__.py
   - [X] __id3v1__.py
   - [X] __image__.py
   - [X] __init__.py
   - [X] __m4a__.py
   - [X] __m4a_atoms__.py
   - [X] __mp3__.py
   - [X] __ogg__.py
   - [X] __shn__.py
   - [X] __vorbis__.py
   - [X] __vorbiscomment__.py
   - [X] __wav__.py
   - [X] __wavpack__.py
   - [X] cue.py
   - [X] delta.py
   - [X] freedb.py
   - [X] musicbrainz.py
   - [X] player.py
   - [X] toc.py
   - [X] ui.py
** DONE Ensure MANIFEST.in is complete
** DONE Update apptest.sh
   It's not much of a test, but it's good to keep it around
   as a last-resort check of things people normally run.
** DONE Run final batch of unit tests on different platforms
   - [X] check Ubuntu
   - [X] check Fedora
   - [X] check Mac OS X
* DONE Finish version 2.19
** DONE Update PCMReader.read() to take a PCM frame count instead of bytes
   Taking a byte count as an argument is a relic from the days
   when most conversion happened through external programs.
   It's time to update this function to work on PCM frames instead
   which is much more natural fit and makes many calculations much easier.
*** DONE Update __init__.py
    - [X] PCMReader.read
    - [X] PCMReaderError.read
    - [X] PCMReaderProgress.read
    - [X] ReorderedPCMReader.read
    - [X] transfer_framelist_data
    - [X] threaded_transfer_framelist_data
    - [X] pcm_cmp
    - [X] pcm_frame_cmp
    - [X] PCMCat.read
    - [X] __buffer__.__init__
    - [X] __buffer__.__len__
    - [X] BufferedPCMReader.read
    - [X] BufferedPCMReader.__fill__
    - [X] LimitedPCMReader.read
    - [X] pcm_split
    - [X] PCMConverter.read
    - [X] ReplayGainReader.read
    - [X] calculate_replay_gain
    - [X] AudioFile.verify
    - [X] PCMReaderWindow.read
    - [X] CDTrackReader.read
    - [X] CDTrackReaderAccurateRipCRC.read
*** DONE Update __aiff__.py
    - [X] AiffReader.read
    - [X] AiffAudio.from_pcm
*** DONE Update __au__.py
    - [X] AuReader.read
    - [X] AuReader.from_pcm
*** DONE Update __flac__.py
    - [X] FlacAudio.__eq__
    - [X] FLAC_Data_Chunk.write
    - [X] FLAC_SSND_Chunk.write
*** DONE Update __shn__.py
    - [X] ShortenAudio.to_wave
    - [X] ShortenAudio.to_aiff
*** DONE Update __wav__.py
    - [X] WaveReader.read
    - [X] WaveAudio.from_pcm
    - [X] WaveAudio.add_replay_gain
*** DONE Update __wavpack__.py
    - [X] WavPackAudio.to_wave
*** DONE Update py_decoders/alac.py
    - [X] ALACDecoder.read
*** DONE Update py_decoders/flac.py
    - [X] FlacDecoder.read
*** DONE Update py_decoders/shn.py
    - [X] SHNDecoder.read
*** DONE Update py_decoders/wavpack.py
    - [X] WavPackDecoder.read
*** DONE Update py_encoders/alac.py
    - [X] encode_mdat
*** DONE Update py_encoders/flac.py
    - [X] encode_flac
*** DONE Update py_encoders/shn.py
    - [X] encode_shn
*** DONE Update py_encoders/wavpack.py
    - [X] encode_wavpack
*** DONE Update src/pcmconv.c
    - [X] pcmreader_read
    - [X] pcmreader_read (alt version)
*** DONE Update src/replaygain.c
    - [X] ReplayGainReader_read
*** DONE Update src/decoders/sine.c
    - [X] Sine_Mono_read
    - [X] Sine_Stereo_read
    - [X] Sine_Simple_read
*** DONE Update test/test.py
    - [X] BLANK_PCM_Reader
    - [X] RANDOM_PCM_Reader
    - [X] EXACT_BLANK_PCM_Reader
    - [X] EXACT_SILENCE_PCM_Reader
    - [X] EXACT_RANDOM_PCM_Reader
    - [X] MD5_Reader
    - [X] Variable_Reader
    - [X] Join_Reader
    - [X] MiniFrameReader
*** DONE Update test/test_core.py
    - [X] BufferedPCMReader.test_pcm
    - [X] LimitedPCMReader.test_read
    - [X] PCMReaderWindow.__test_reader__
    - [X] PCM_Reader_Multiplexer.read
    - [X] TestMultiChannel.__test_assignment__
*** DONE Update test/test_formats.py
    - [X] ERROR_PCM_Reader.read
    - [X] ALACFileTest.__test_reader__
    - [X] ALACFileTest.__test_reader_nonalac__
    - [X] ALACFileTest.test_streams
    - [X] FlacFileTest.test_streams
    - [X] FlacFileTest.__test_reader__
    - [X] ShortenFileTest.test_streams
    - [X] ShortenFileTest.__test_reader__
    - [X] WavPackFileTest.__test_reader__
*** DONE Update test/test_streams.py
    - [X] FrameListReader.read
    - [X] MD5Reader.read
    - [X] Sine8_Mono.read
    - [X] Sine8_Stereo.read
    - [X] Simple_Sine.read
    - [X] WastedBPS16.read
*** DONE Ensure all unit tests pass
**** DONE [Lib]
     - [X] core
     - [X] cuesheet
     - [X] freedb
     - [X] image
     - [X] musicbrainz
     - [X] pcm
     - [X] bitstream
     - [X] replaygain
     - [X] resample
     - [X] tocfile
     - [X] verify
     - [X] player
**** DONE [Format]
     - [X] audiofile
     - [X] lossless
     - [X] lossy
     - [X] aiff
     - [X] alac
     - [X] au
     - [X] dvda
     - [X] flac
     - [X] m4a
     - [X] mp2
     - [X] mp3
     - [X] oggflac
     - [X] shorten
     - [X] sines
     - [X] vorbis
     - [X] wave
     - [X] wavpack
**** DONE [Metadata]
     - [X] metadata
     - [X] flac
     - [X] wavpack
     - [X] id3v1
     - [X] id3v2
     - [X] vorbis
     - [X] m4a
**** DONE [Util]
     - [X] audiotools_config
     - [X] cd2track
     - [X] cdinfo
     - [X] cdplay
     - [X] coverdump
     - [X] coverview
     - [X] dvda2track
     - [X] dvdainfo
     - [X] track2cd
     - [X] track2track
     - [X] trackcat
     - [X] trackcmp
     - [X] trackinfo
     - [X] tracklength
     - [X] tracklint
     - [X] trackplay
     - [X] trackrename
     - [X] tracksplit
     - [X] tracktag
     - [X] trackverify
*** DONE Update reference documentation
** DONE Remove the big pile of imports from various modules
   Only import the stuff we need, when we need it.
*** DONE __init__.py
*** DONE accuraterip.py
*** DONE aiff.py
*** DONE ape.py
*** DONE au.py
*** DONE dvda.py
*** DONE flac.py
*** DONE id3.py
*** DONE id3v1.py
*** DONE image.py
*** DONE m4a.py
*** DONE m4a_atoms.py
*** DONE mp3.py
*** DONE ogg.py
*** DONE shn.py
*** DONE vorbis.py
*** DONE vorbiscomment.py
*** DONE wav.py
*** DONE wavpack.py
*** DONE cue.py
*** DONE delta.py
*** DONE freedb.py
*** DONE musicbrainz.py
*** DONE player.py
*** DONE toc.py
*** DONE ui.py
*** DONE ensure unit tests pass
**** DONE [Lib]
     - [X] core
     - [X] cuesheet
     - [X] freedb
     - [X] image
     - [X] musicbrainz
     - [X] pcm
     - [X] bitstream
     - [X] replaygain
     - [X] resample
     - [X] tocfile
     - [X] verify
     - [X] player
**** DONE [Format]
     - [X] audiofile
     - [X] lossless
     - [X] lossy
     - [X] aiff
     - [X] alac
     - [X] au
     - [X] dvda
     - [X] flac
     - [X] m4a
     - [X] mp2
     - [X] mp3
     - [X] oggflac
     - [X] shorten
     - [X] sines
     - [X] vorbis
     - [X] wave
     - [X] wavpack
**** DONE [Metadata]
     - [X] metadata
     - [X] flac
     - [X] wavpack
     - [X] id3v1
     - [X] id3v2
     - [X] vorbis
     - [X] m4a
**** DONE [Util]
     - [X] audiotools_config
     - [X] cd2track
     - [X] cdinfo
     - [X] cdplay
     - [X] coverdump
     - [X] coverview
     - [X] dvda2track
     - [X] dvdainfo
     - [X] track2cd
     - [X] track2track
     - [X] trackcat
     - [X] trackcmp
     - [X] trackinfo
     - [X] tracklength
     - [X] tracklint
     - [X] trackplay
     - [X] trackrename
     - [X] tracksplit
     - [X] tracktag
     - [X] trackverify
** DONE Integrate Filename object into utilities
   This object should replace the Messenger.filename classmethod.
   It automatically performs filename -> unicode conversion
   and, when files are on disk, compares for equality by device/inode.
*** DONE Update utilities
**** DONE audiotools-config
**** DONE cd2track
**** DONE coverdump
**** DONE coverview
**** DONE dvda2track
**** DONE dvdainfo
**** DONE track2cd
**** DONE track2track
**** DONE trackcat
**** DONE trackcmp
**** DONE trackinfo
**** DONE tracklint
**** DONE trackrename
**** DONE tracksplit
**** DONE tracktag
**** DONE trackverify
*** DONE Add documentation for Filename
*** DONE Add unit tests for Filename
*** DONE Remove Messenger.filename classmethod
**** DONE remove mention in documentation
*** DONE Ensure all unit tests pass
** DONE Sanity check tool inputs/outputs
*** DONE Ensure input files are included only once
    - [X] track2cd - generate warning
    - [X] track2track - generate error
    - [X] trackcat - generate warning
    - [X] trackcmp - short-circuit same file comparison
    - [X] tracklength - generate warning
    - [X] tracklint - generate error
    - [X] trackrename - generate error
    - [X] tracktag - generate error
    - [X] trackverify - skip duplicates
**** DONE Unit test new behavior
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] tracklint
    - [X] trackrename
    - [X] tracktag
    - [X] trackverify
*** DONE Ensure input file(s) are different from output file(s)
    Overwriting files is okay by default (in the Unix tradition)
    but input and output as same file is not and should generate error.
    - [X] coverdump
    - [X] track2track
    - [X] trackcat
    - [X] tracksplit
**** DONE Unit test new behavior
     - [X] coverdump
     - [X] track2track
     - [X] trackcat
     - [X] tracksplit
*** DONE Ensure same file isn't written twice by the same utility
    This would typically be the result of a misused "--format" argument.
    - [X] cd2track
    - [X] dvda2track
    - [X] track2track
    - [X] trackrename
    - [X] tracksplit
**** DONE unit test new behavior
     - [X] cd2track
     - [X] track2track
     - [X] trackrename
     - [X] tracksplit
** DONE Ensure progress display doesn't overload terminal with rows
   As more cores become commonplace, it's important not to
   overload the screen with too many progress rows
   in case the number of simultaneous jobs
   exceeds the number of terminal rows.
** DONE Improve metadata tagging widget
   I'm not convinced the current opened/closed bottom window
   is the ideal design for editing extended album/track metadata.
   Something more similar to a spreadsheet would be ideal
   but that's complicated by the serious lack of space
   in terminal windows.
** DONE Fix ReplayGain to work on files with different sample rates
   This should handle a wider array of cases than it does now.
*** DONE Update can_add_replay_gain classmethod
    It should take a list of tracks and return True
    if ReplayGain can be added to them, False if not
    which takes the place of applicable_replay_gain
    - [X] AudioFile.can_add_replay_gain
    - [X] FlacAudio.can_add_replay_gain
    - [X] M4AAudio_faac.can_add_replay_gain
    - [X] MP3Audio.can_add_replay_gain
    - [X] VorbisAudio.can_add_replay_gain
    - [X] WaveAudio.can_add_replay_gain
    - [X] WavPackAudio.can_add_replay_gain
**** DONE update reference documentation
**** DONE update unit tests
     - [X] test_formats.py  AudioFileTest.test_replay_gain
     - [X] test_utils.py    track2track.test_options
     - [X] test_utils.py    tracktag.test_options
*** DONE Add supports_replay_gain() classmethod
    Returns True if the class supports ReplayGain of any kind.
    - [X] AudioFile
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
**** DONE Add documentation
*** DONE Remove audiotools.applicable_replay_gain function
    this functionality is shifted to can_add_replay_gain
**** DONE remove from utilities
     - [X] dvda2track
     - [X] track2track
     - [X] tracksplit
**** DONE update reference documentation
**** DONE update unit tests
*** DONE Update calculate_replay_gain function
**** DONE tracks with unsupported sample rates should be resampled
     according to the nearest supported sample rate available
**** DONE tracks with different sample rates should be resampled
     according to the most common sample rate available
**** DONE tracks with more than two channels should be culled
     remove any channels above the first two during calculation
**** DONE update unit tests
     - [X] test_core.py  TestReplayGain.test_basics
*** DONE Update add_replay_gain classmethods as needed
    - [X] AudioFile.add_replay_gain
    - [X] FlacAudio.add_replay_gain
    - [X] M4AAudio_faac.add_replay_gain
    - [X] MP3Audio.add_replay_gain
    - [X] VorbisAudio.add_replay_gain
    - [X] WaveAudio.add_replay_gain
    - [X] WavPackAudio.add_replay_gain
*** DONE Update utilities to use new ReplayGain application procedure
    Given a list of tracks for a given album,
    if all are the same format and can_add_replay_gain returns True,
    queue a call to add_replay_gain on those tracks.
**** DONE audiotools-config
**** DONE cd2track
**** DONE dvda2track
**** DONE track2track
**** DONE tracksplit
**** DONE tracktag
** DONE Display X/Y progress during operations
   When a track is finished transcoding, for instance, output:
   [ 2 / 15 ] input.wav -> output.mp3
   or something similar.
*** DONE Update utilities
    - [X] cd2track
    - [X] dvda2track
    - [X] track2track
    - [X] trackcmp
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
*** DONE Update unit tests
    - [X] cd2track
    - [X] dvda2track
    - [X] track2track
    - [X] trackcmp
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
** DONE Update metadata documentation with examples
*** DONE FLAC
*** DONE MP3
    - [X] ID3v1/ID3v1.1
    - [X] ID3v2.2
    - [X] ID3v2.3
    - [X] ID3v2.4
*** DONE APEv2
*** DONE M4A
** DONE Remove Construct module
   Convert all usage of Construct to BitstreamReader/BitstreamWriter.
   Since this is unlikely to be updated for Python 3,
   removing it should smooth that inevitable transition.
*** DONE __accuraterip__.py
*** DONE __aiff__.py
    - [X] chunks
    - [X] get_metadata
    - [X] set_metadata
    - [X] delete_metadata
    - [X] to_pcm
    - [X] from_pcm
    - [X] pcm_split
    - [X] aiff_from_chunks
**** DONE AiffReader
     - [X] __init__
     - [X] read
*** DONE __ape__.py
*** DONE __au__.py
*** DONE cue.py
*** DONE __dvda__.py
**** DONE DVDAudio
     - [X] __titlesets__
     - [X] __titles__
**** DONE DVDATitle
     - [X] __parse_info__
*** DONE __flac__.py
*** DONE __freedb__.py
*** DONE __id3__.py
*** DONE __id3v1__.py
*** DONE __image__.py
    - [X] __JPEG__
    - [X] __PNG__
    - [X] __GIF__
    - [X] __BMP__
    - [X] __TIFF__
**** DONE check these against PIL's output
**** DONE check against truncated images
*** DONE __init__.py
*** DONE __m4a__.py
**** DONE M4ATaggedAudio
     - [X] get_metadata
     - [X] set_metadata
     - [X] delete_metadata
**** DONE M4A_META_Atom
     - [X] __init__
     - [X] __repr__
     - [X] parse
     - [X] __getattr__
     - [X] __setattr__
     - [X] __delattr__
     - [X] images
     - [X] add_image
     - [X] delete_image
     - [X] converted
     - [X] __comment_name__
     - [X] supports_images
     - [X] __by_pair__
     - [X] __comment_pairs__
     - [X] clean
**** DONE M4AAudio_faac
     - [X] __init__
     - [X] is_type
**** DONE ALACAudio
     - [X] __init__
     - [X] is_type
     - [X] to_pcm
     - [X] from_pcm
     - [X] __ftyp_atom__
     - [X] __moov_atom__
     - [X] __free_atom__
*** DONE __m4a_atoms__.py
    Re-implement atom parsing/building without Construct
**** DONE ftyp
**** DONE moov
***** DONE mvhd
***** DONE trak
****** DONE tkhd
****** DONE mdia
******* DONE mdhd
******* DONE hdlr
******* DONE minf
******** DONE smhd
******** DONE dinf
********* DONE dref
******** DONE stbl
********* DONE stsd
********** DONE alac
********* DONE stts
********* DONE stsc
********* DONE stsz
********* DONE stco
***** DONE udta
****** DONE meta
**** DONE free
**** DONE Rename __m4a_atoms2__.py to __m4a_atoms__.py
*** DONE __mp3__.py
**** DONE MP3
     - [X] __init__
     - [X] is_type
     - [X] __find_next_mp3_frame__
     - [X] __find_mp3_start__
     - [X] __find_last_mp3_frame__
     - [X] verify
**** DONE MP2
     - [X] is_type
*** DONE __musepack__.py
*** DONE __musicbrainz__.py
*** DONE __ogg__.py
*** DONE player.py
*** DONE __shn__.py
*** DONE toc.py
*** DONE __vorbis__.py
**** DONE VorbisAudio
     - [X] __read_metadata__
     - [X] total_frames
     - [X] get_metadata
     - [X] set_metadata
**** DONE Remove cruft
     - [X] OggStreamReader
     - [X] OggStreamWriter
*** DONE __vorbiscomment__.py
*** DONE __wav__.py
*** DONE __wavpack__.py
*** DONE test/test_core.py
**** DONE TestFrameList
     - [X] test_8bit_roundtrip
     - [X] test_16bit_roundtrip
     - [X] test_24bit_roundtrip
*** DONE test/test_formats.py
**** DONE ALACFileTest
     - [X] test_blocksizes
**** DONE FlacFileTest
     - [X] test_blocksizes
**** DONE ShortenFileTest
     - [X] test_blocksizes
**** DONE WavpackFileTest
     - [X] test_blocksizes
** DONE Replace gettext with string constants
   Transforming _(u"some text") to SOME_TEXT
   where SOME_TEXT is a predefined unicode string
   in an audiotools sub-module.
   This makes text more consistent *and* easier to modify.
*** DONE Update utilities
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
*** DONE Update modules
    - [X] __init__.py
    - [X] aiff.py
    - [X] ape.py
    - [X] au.py
    - [X] cue.py
    - [X] dvda.py
    - [X] flac.py
    - [X] image.py
    - [X] m4a.py
    - [X] m4a_atoms.py
    - [X] mp3.py
    - [X] ogg.py
    - [X] toc.py
    - [X] ui.py
    - [X] vorbis.py
    - [X] vorbiscomment.py
    - [X] wav.py
    - [X] wavpack.py
*** DONE Remove gettext
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
    - [X] audiotools/__init__.py
    - [X] audiotools/aiff.py
    - [X] audiotools/ape.py
    - [X] audiotools/au.py
    - [X] audiotools/cue.py
    - [X] audiotools/flac.py
    - [X] audiotools/id3.py
    - [X] audiotools/image.py
    - [X] audiotools/m4a.py
    - [X] audiotools/mp3.py
    - [X] audiotools/toc.py
    - [X] audiotools/vorbis.py
    - [X] audiotools/wav.py
    - [X] audiotools/wavpack.py
*** DONE Update unit tests
    - [X] test_formats.py
    - [X] test_metadata.py
    - [X] test_utils.py
*** DONE Reduce big import chunks
    Convert "from audiotools.text import (CONSTANT, ...)"
    to "import audiotools.text as t" and "t.CONSTANT"
    to avoid having huge import blocks at the start of utilities.
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
*** DONE Ensure unit tests pass
** DONE Update C-based ReplayGainReader
   Use a lot of the new C-based facilities to make it simpler.
** DONE Fix --number=0 argument to tracktag
   I'd like to make track_number=0 a valid value
   and have track_number=None indicate a missing field.
   The problem is maintaining consistency between
   metadata formats that store track numbers as text (like ID3v2)
   and those that store track numbers as integers (like M4A).
   - [X] MetaData
   - [X] ApeTag
   - [X] FlacMetaData
   - [X] ID3CommentPair
   - [X] ID3v1Comment
   - [X] ID3v22Comment
   - [X] ID3v23Comment
   - [X] ID3v24Comment
   - [X] M4A_META_Atom
   - [X] VorbisComment
*** DONE Update utilities
*** DONE Update documentation
*** DONE Update unit tests
**** DONE Add getitem/setitem/getattr/setattr/delattr tests to metadata
     - [X] flac
     - [X] wavpack
     - [X] id3v1
     - [X] id3v2
     - [X] vorbis
     - [X] m4a
** DONE Remove Python Imaging Library requirement
   This is only used for thumbnailing
   and for TKinter-based image display.
   Since a Python3 version doesn't seem to be pending,
   it would be better to remove the requirement.
*** DONE Remove thumbnail functions from audiotools.image
    - [X] can_thumbnail
    - [X] thumbnail_formats
    - [X] thumbnail_image
*** DONE Remove thumbnail method from audiotools.Image
*** DONE Remove thumbnail config options
    - [X] audiotools.THUMBNAIL_FORMAT
    - [X] audiotools.THUMBNAIL_SIZE
*** DONE Update utilities
    - [X] audiotools-config
    - [X] track2track
    - [X] tracktag
*** DONE Update utility man pages
    - [X] audiotools-config
    - [X] track2track
    - [X] tracktag
*** DONE Update programming documentation
*** DONE Update unit tests
** DONE Update cuesheet interface to handle non-CD sheets
   Though rare, non-CD audio is sometimes combined
   with cuesheets and should be handled properly
   in those instances.
*** DONE Add sample_rate field to cuesheet pcm_lengths() method
    - [X] Cuesheet.pcm_lengths
    - [X] Flac_CUESHEET.pcm_lengths
    - [X] TOCFile.pcm_lengths
**** DONE Update documentation
**** DONE Update unit tests
*** DONE Remove sheet_to_unicode function
*** DONE Update utilities to use sample_rate field with pcm_lengths
    - [X] trackinfo
    - [X] tracksplit
    - [X] tracktag
*** DONE Ensure unit tests pass
** DONE Add faster and more accurate audio type identifier
   Instead of checking open files on a format-by-format basis
   to determine its type via looping, it's more effecient
   to check for all possible file types from the same stream simultaneously
   via a sort of finite automata.
*** DONE Add file_type function
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio - without ID3v2 tag
    - [X] M4AAudio
    - [X] MP2Audio - without ID3v2 tag
    - [X] MP3Audio - without ID3v2 tag
    - [X] OggFlacAudio
    - [X] ShortenAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
    - [X] FlacAudio - with ID3v2 tag
    - [X] MP2Audio - with ID3v2 tag
    - [X] MP3Audio - with ID3v2 tag
*** DONE Update functions which use is_type method to use file_type function
    - [X] trackverify
    - [X] audiotools.open
*** DONE Remove is_type method from AudioFile classes
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] ShortenAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Update programming documentation
*** DONE Update unit tests
    - [X] test_formats.py
** DONE Update utilities to handle broken track_name template fields
   - [X] cd2track
   - [X] dvda2track
   - [X] track2track
   - [X] trackrename
   - [X] tracksplit
*** DONE Add unit tests for broken track_name template fields
** DONE Split off tracktag's functionality
*** DONE Move the image options into a specialized tool (covertag?)
    Its interactive mode should use PyGTK/Tkinter
    since it's helpful to see the images one is selecting to add/remove.
    - [X] --remove-images
    - [X] --front-cover
    - [X] --back-cover
    - [X] --leaflet
    - [X] --media
    - [X] --other-image
    - [X] -T, --thumbnail
*** DONE Move CD options into a specialized tool
    Something for tagging a group of tracks as if they're an entire CD
    - [X] --cue
    - [X] -l, --lookup
    - [X] --musicbrainz-server
    - [X] --musicbrainz-port
    - [X] --no-musicbrainz
    - [X] --freedb-server
    - [X] --freedb-port
    - [X] --no-freedb
** DONE Add interative mode to audiotools-config
   This is one instance where seeing all the options "up-front"
   is likely to make the process easier.
** DONE Add Python-based file decoders
   These would be low-performance, Python-based PCMReader-style objects
   demonstrating how the decoding process works in a relatively simple
   manner through the use of the BitstreamReader objects.
   They would also provide reference implementations.
*** DONE py_decoders/FlacDecoder
*** DONE py_decoders/SHNDecoder
*** DONE py_decoders/ALACDecoder
*** DONE py_decoders/WavPackDecoder
** DONE Update .convert() method to use fewer temporary files
   .wav and .aiff containers with embedded chunks
   currently route data through actual .wav/.aiff files
   in order to pass those chunks to another format.
   It would be better to avoid creating an intermediate
   file whenever possible.
*** DONE Update wav containers to use new interface
    WaveContainer.has_foreign_wav_chunks()
    returns True if the instance has chunks to convert
    WaveContainer.header_footer()
    returns (header, footer) binary strings
    WaveContainer.from_wave(header, pcmreader, footer)
    returns new instance built from heaeder, PCM data and footer

    Once interface is in place, .convert() can pass header/footer
    and wrap progress monitor around pcmreader in order to avoid
    temporary wav files.
**** DONE Update FlacAudio
     - [X] has_foreign_wave_chunks()
     - [X] wave_header_footer()
     - [X] from_wave(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update OggFlacAudio
     - [X] has_foreign_wave_chunks()
     - [X] wave_header_footer()
     - [X] from_wave(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update ShortenAudio
     - [X] has_foreign_wave_chunks()
     - [X] wave_header_footer()
     - [X] from_wave(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update WavPackAudio
     - [X] has_foreign_wave_chunks()
     - [X] wave_header_footer()
     - [X] from_wave(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update WaveAudio
     - [X] has_foreign_wave_chunks()
     - [X] wave_header_footer()
     - [X] from_wave(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update programming documentation
**** DONE Update unit tests
*** DONE Update aiff containers to use new interface
    AiffContainer.has_foreign_aiff_chunks()
    returns True if the instance has chunks to convert

    AiffContainer.header_footer()
    returns (header, footer) binary strings

    AiffContainer.from_aiff(header, pcmreader, footer)
    returns new instance built from heaeder, PCM data and footer

    Once interface is in place, .convert() can pass header/footer
    and wrap progress monitor around pcmreader in order to avoid
    temporary aiff files.
**** DONE Update AiffAudio
     - [X] has_foreign_aiff_chunks()
     - [X] aiff_header_footer()
     - [X] from_aiff(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update FlacAudio
     - [X] has_foreign_aiff_chunks()
     - [X] aiff_header_footer()
     - [X] from_aiff(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update OggFlacAudio
     - [X] has_foreign_aiff_chunks()
     - [X] aiff_header_footer()
     - [X] from_aiff(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update ShortenAudio
     - [X] has_foreign_aiff_chunks()
     - [X] aiff_header_footer()
     - [X] from_aiff(filename, header, pcmreader, footer, compression)
     - [X] convert(target_page, target_class, compression, progress)
**** DONE Update programming documentation
**** DONE Update unit tests
** DONE Add pause()/resume() methods to low-level output players
   This will hopefully make pausing more responsive
   and work better than simply emptying the output buffer.
** DONE Ensure unicode command-line arguments are parsed properly
*** DONE cd2track
    - [X] --format
    - [X] --dir
*** DONE coverdump
    - [X] filename arguments
    - [X] --dir
    - [X] --prefix
*** DONE covertag
    - [X] filename arguments
    - [X] --front-cover
    - [X] --back-cover
    - [X] --leaflet
    - [X] --media
    - [X] --other-image
*** DONE track2track
    - [X] filename arguments
    - [X] --dir
    - [X] --format
    - [X] --output
*** DONE trackcat
    - [X] filename arguments
    - [X] --output
    - [X] --cue
*** DONE trackcmp
    - [X] filename arguments
*** DONE trackinfo
    - [X] filename arguments
*** DONE tracklength
    - [X] filename arguments
*** DONE tracklint
    - [X] filename arguments
    - [X] --db
*** DONE trackrename
    - [X] filename arguments
    - [X] --format
*** DONE tracksplit
    - [X] filename arguments
    - [X] --cue
    - [X] --dir
    - [X] --format
*** DONE tracktag
    - [X] filename arguments
    - [X] --name
    - [X] --artist
    - [X] --album
    - [X] --performer
    - [X] --composer
    - [X] --conductor
    - [X] --catalog
    - [X] --ISRC
    - [X] --publisher
    - [X] --media-type
    - [X] --year
    - [X] --date
    - [X] --copyright
    - [X] --comment
    - [X] --comment-file
** DONE Add unit tests for Python-based decoders
   Since Python-based codecs are so much slower
   it's impossible to make these as comprehensive as the C-based tests.
   So we'll only be able to test the basics using very small streams
   against the output of the C-based decoders.
*** DONE FlacDecoder
*** DONE ALACDecoder
*** DONE WavPackDecoder
*** DONE SHNDecoder
** DONE Add unit tests for Python-based encoders
*** DONE encode_flac
*** DONE encode_mdat
*** DONE encode_wavpack
*** DONE encode_shn
** DONE Ensure Python files are PEP8-compliant
*** DONE executables
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] covertag
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
*** DONE libraries
    - [X] __init__.py
    - [X] accuraterip.py
    - [X] aiff.py
    - [X] ape.py
    - [X] au.py
    - [X] cue.py
    - [X] delta.py
    - [X] dvda.py
    - [X] flac.py
    - [X] freedb.py
    - [X] id3.py
    - [X] id3v1.py
    - [X] image.py
    - [X] m4a.py
    - [X] m4a_atoms.py
    - [X] mp3.py
    - [X] musicbrainz.py
    - [X] ogg.py
    - [X] opus.py
    - [X] player.py
    - [X] shn.py
    - [X] text.py
    - [X] toc.py
    - [X] ui.py
    - [X] vorbis.py
    - [X] vorbiscomment.py
    - [X] wav.py
    - [X] wavpack.py
**** DONE py_decoders
     - [X] __init__.py
     - [X] alac.py
     - [X] flac.py
     - [X] shn.py
     - [X] wavpack.py
**** DONE py_encoders
     - [X] __init__.py
     - [X] alac.py
     - [X] flac.py
     - [X] shn.py
     - [X] wavpack.py
** DONE Have interactive modes clear screen after finishing
   Some systems leave the Urwid-built screens half-cleared
   before resuming regular output.
   It's preferable to erase the whole thing in that case.
   - [X] audiotools-config
   - [X] cd2track
   - [X] cdplay
   - [X] dvda2track
   - [X] track2track
   - [X] trackcat
   - [X] trackplay
   - [X] trackrename
   - [X] tracksplit
   - [X] tracktag
** DONE Have interactive modes detect termios.error at launch
   This seems to be caused mostly by opening interactive modes
   with arguments piped from xargs
   - [X] audiotools-config
   - [X] cd2track
   - [X] cdplay
   - [X] dvda2track
   - [X] track2track
   - [X] trackcat
   - [X] trackplay
   - [X] trackrename
   - [X] tracksplit
   - [X] tracktag
** DONE Spellcheck programming documentation
   - [X] audiotools.rst
   - [X] audiotools_bitstream.rst
   - [X] audiotools_cdio.rst
   - [X] audiotools_cue.rst
   - [X] audiotools_pcm.rst
   - [X] audiotools_pcmconverter.rst
   - [X] audiotools_player.rst
   - [X] audiotools_replaygain.rst
   - [X] audiotools_toc.rst
   - [X] index.rst
   - [X] metadata.rst
** DONE Spellcheck reference documentation
   - [X] aiff.tex
   - [X] alac.tex
   - [X] ape.tex
   - [X] apev2.tex
   - [X] au.tex
   - [X] basics.tex
   - [X] dvda2.tex
   - [X] flac.tex
   - [X] freedb.tex
   - [X] introduction.tex
   - [X] license.tex
   - [X] m4a.tex
   - [X] mp3.tex
   - [X] musepack.tex
   - [X] musicbrainz.tex
   - [X] musicbrainz_mmd.tex
   - [X] ogg.tex
   - [X] oggflac.tex
   - [X] references.tex
   - [X] replaygain.tex
   - [X] shorten.tex
   - [X] speex.tex
   - [X] vorbis.tex
   - [X] wav.tex
   - [X] wavpack.tex
** DONE Spellcheck reference figures
** DONE Spellcheck manual pages
** DONE Split reference documentation into sections by file
   That is, massive codecs should be split into individual
   .tex files by decode/encode sections and then subdivided
   further as necessary to keep them from getting too fragile/unweildy
*** DONE Shorten
    - [X] decode
    - [X] encode
*** DONE FLAC
**** DONE metadata
**** DONE decode
**** DONE encode
     - [X] fixed
     - [X] residual
     - [X] lpc
*** DONE WavPack
**** DONE decode
     - [X] terms
     - [X] weights
     - [X] samples
     - [X] entropy
     - [X] bitstream
     - [X] decorrelation
**** DONE encode
     - [X] correlation
     - [X] terms
     - [X] weights
     - [X] samples
     - [X] entropy
     - [X] bitstream
*** DONE ALAC
**** DONE decode
**** DONE encode
     - [X] atoms
     - [X] lpc
     - [X] residual
*** DONE Remove m4 requirement
    This is massive overkill for what little we need it for,
    which is generating audioformats-_.tex and _-codec.tex files
    with header/footers included and paper size populated.

    It's better to use Python for that trivial templating instead.
** DONE Tweak reference documentation layout
   Try harder to get pseudocode, file diagram,
   bit diagram and example on same pair of pages.
*** DONE Shorten
*** DONE FLAC
*** DONE WavPack
*** DONE ALAC
*** DONE DVD-A
** DONE Ensure documentation flows correctly
   Pages should start/end properly at letter and A4 paper sizes.
   - [X] letter
   - [X] A4
** DONE Handle multi-channel .opus files properly
*** DONE Add unit tests
** DONE Add optional interactive modes to utilities
   Now that Urwid is being used for editxmcd, it might be helpful
   to add optional console-based interactive modes to the other tools.
   This may improve ease-of-use (particularly discoverability
   in the case of format and quality options) without sacrificing
   scriptability or command-line power.

   Just as the command-line options are kept as consistent as possible,
   all interactive modes will also need a consistent interface.
*** DONE audiotools-config
*** DONE cd2track
*** DONE cdplay
**** DONE make player widget generic
*** DONE dvda2track
*** DONE track2track
*** DONE trackcat
*** DONE trackplay
**** DONE make player widget generic
*** DONE trackrename
*** DONE tracksplit
*** DONE tracktag
** DONE run codecs through valgrind
   Try to ensure there's no hidden bugs in the low-level C code.
*** DONE decoders
    Will need to assemble standalone decoders for this.
    - [X] alac
    - [X] flac
    - [X] mlp
    - [X] oggflac
    - [X] shorten
    - [X] wavpack
*** DONE encoders
    - [X] alac
    - [X] flac
    - [X] shorten
    - [X] wavpack
*** DONE Update standalone encoders to take command-line arguments
    This would make them easier to memory test.
    - [X] alac
    - [X] flac
    - [X] shorten
    - [X] wavpack
** DONE Have cdplay play properly
** DONE Update version number to 2.19 final
* DONE Finish version 2.20
** DONE Remove track number/album number guessing heuristics
   Don't try to guess number from filename.
   If a track number is needed, build it from the order
   in which the files are submitted on the command line.
*** DONE track_number()
*** DONE album_number()
** DONE Update track2track/tracktag to use better option filler widget
   cd2track, dvda2track and tracksplit always work with one album
   at a time, pretty much by definition.
   track2track may handle multiple albums simultaneously
   and currently makes mutiple calls to urwid to work.
   A better approach is to have all the metadata lookups
   performed first, multiple metadata edit screens,
   and a single output options/preview screen.
** DONE Simplify PCMReaderWindow
   Split this into a PCMReaderHead and PCMReaderDeHead pair.

   The former handles the "pcm_frames" argument,
   either truncating a PCMReader or appending silence as needed.

   The latter handles the "initial_offset" argument,
   either chopping off frames or prepending silence as needed.
** DONE Adjust BitstreamReader to better handle huge byte counts
   These are typically done in error and shouldn't eat up
   all the RAM in the world before failing.
*** DONE Update functions
    - [X]  br_substream_append_f
    - [X]  br_substream_append_e
    - [X]  BitstreamReader_read_bytes
    - [X]  BitstreamReader_substream_meth
    - [X]  BitstreamReader_substream_append
*** DONE Add unit tests
    - [X]  br_substream_append_f
    - [X]  br_substream_append_e
    - [X]  BitstreamReader_read_bytes
    - [X]  BitstreamReader_substream_meth
    - [X]  BitstreamReader_substream_append
*** DONE turn the current buf_append into buf_extend
*** DONE Replace buf_extend with buf_append
** DONE Have process-based decoders exit when data runs out
   That is, have PCMReader's .read() call subprocess.wait()
   when the framelists end rather than waiting for .close()
   to be called (which typically isn't).
   If the wait fails, have it raise an exception at that point.
*** DONE Update process-based formats
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OpusAudio
*** DONE Update documentation
*** DONE Ensure unit tests pass
** DONE Reduce stdint usage in bitstream library
   Try to limit stdint variables to places
   where it's necessary, like read_64 and read_bytes
*** DONE Convert state/context to state_t
**** DONE Update br_huffman_table
**** DONE Update BitstreamReader
**** DONE struct read_bits
**** DONE struct unread_bit
**** DONE struct read_unary
**** DONE struct read_limited_unary
*** DONE Update jump table structures
**** DONE read_bits
**** DONE unread_bit
**** DONE read_unary
**** DONE read_limited_unary
*** DONE Update br_huffman_table
**** DONE Update continue_ to int
*** DONE Update bs_buffer
**** DONE unsigned data_size
**** DONE unsigned window_start
**** DONE unsigned window_end
**** DONE buf_resize()
**** DONE buf_write()
**** DONE buf_read()
*** DONE Update br_mark
**** DONE unsigned substream
**** DONE unsigned external
*** DONE Update substream_append()
**** DONE br_substream_append_f
**** DONE br_substream_append_s
**** DONE br_substream_append_e
**** DONE br_substream_append_c
*** DONE Update bw_external_output
**** DONE buffer_size
**** DONE bw_open_external()
**** DONE ext_open_w()
** DONE Add bs_buffer-specific unit tests to bitstream.c
   - [X] buf_resize
   - [X] buf_write
   - [X] buf_read
   - [X] buf_copy
   - [X] buf_extend
   - [X] buf_reset
   - [X] buf_getc
   - [X] buf_putc
   - [X] buf_getpos
   - [X] buf_setpos
   - [X] buf_set_rewindable
   - [X] BUF_WINDOW_SIZE
   - [X] BUF_WINDOW_START
   - [X] BUF_WINDOW_END
** DONE Fix cuesheet handling
   Ensure the cuesheet interface handles unusual cases more accurately
   and build a proper base class for the .cue and .toc files
*** DONE Add new base classes
    - [X] Sheet
    - [X] SheetTrack
    - [X] SheetIndex
*** DONE Update cue module
    - [X] read_cuesheet
    - [X] write_cuesheet
*** DONE Update toc module
    - [X] read_tocfile
    - [X] write_tocfile
*** DONE Update get_cuesheet methods
    - [X] FlacAudio
    - [X] TrueAudio
    - [X] WavPackAudio
*** DONE Update set_cuesheet methods
    - [X] FlacAudio
    - [X] TrueAudio
    - [X] WavPackAudio
*** DONE Update documentation
    - [X] read_sheet
    - [X] Sheet
    - [X] SheetTrack
    - [X] SheetIndex
*** DONE Update utilities using cuesheet interface
    - [X] track2cd
    - [X] trackcat
    - [X] trackinfo
    - [X] tracksplit
*** DONE Update unit tests
*** DONE Add unit test for FlacAudio.set_cuesheet()
    Ensure the resulting Flac_CUESHEET block matches
    the one generated by metaflac --import-cuesheet-from
** DONE Add optional total_pcm_frames argument to from_pcm()
   Certain formats encode much easier if the total number of PCM frames
   is known in advance.  Adding a total_pcm_frames argument
   allows encoders to make use of that info if it's available.
*** DONE Update formats with total_pcm_frames argument
**** DONE AudioFile
     does nothing
**** DONE ALACAudio
     Builds placeholder seektable which is then populated later
**** DONE AiffAudio
     does nothing
**** DONE AuAudio
     does nothing
**** DONE FlacAudio
     Allocates enough space for populated seektable
     on top of any leftover for VORBIS tags.
**** DONE M4AAudio
     does nothing
**** DONE MP2Audio
     does nothing
**** DONE MP3Audio
     does nothing
**** DONE OggFlacAudio
     does nothing
**** DONE OpusAudio
     does nothing
**** DONE ShortenAudio
     If total_pcm_frames argument is given to from_pcm(),
     precalculate Wave header rather than use temporary file.
     This shouldn't require updating the encode_shn function.
**** DONE TrueAudio
     If total_pcm_frames argument is given to from_pcm(),
     fill header and temporary seektable rather than use temporary file.
     This shouldn't require updating the encode_tta function.
**** DONE VorbisAudio
     does nothing
**** DONE WavPackAudio
     If total_pcm_frames argument is given to from_pcm(),
     fill blocks during encoding rather than re-fill them afterward.
     This will require minor update to encode_wavpack function.
**** DONE WaveAudio
     does nothing
*** DONE Update convert() methods to call from_pcm() with argument
    But only if the input file is lossless.
**** DONE AudioFile
**** DONE ALACAudio
**** DONE AiffAudio
**** DONE AuAudio
**** DONE FlacAudio
**** DONE M4AAudio
**** DONE MP2Audio
**** DONE MP3Audio
**** DONE OggFlacAudio
**** DONE OpusAudio
**** DONE ShortenAudio
**** DONE TrueAudio
**** DONE VorbisAudio
**** DONE WavPackAudio
**** DONE WaveAudio
*** DONE Update utilities to make use of total_pcm_frames argument
**** DONE cd2track
**** DONE dvda2track
**** DONE trackcat
**** DONE tracksplit
*** DONE Document total_pcm_frames argument
*** DONE Unit test calling formats with total_pcm_frames and without
*** DONE Add unit tests with total_pcm_frames and without per encoder
    - [X]  ALACAudio.from_pcm()
    - [X]  FlacAudio.from_pcm()
    - [X]  ShnAudio.from_pcm()
    - [X]  TTAAudio.from_pcm()
    - [X]  WavPackAudio.from_pcm()
** DONE Make decoder streams seekable
   PCMReader.seek(pcm_frames) -> new pcm frames position
   could seek to the closest position in the stream to the given frames
   without going over, and return the current position in PCM frames.
*** DONE Update decoders
**** DONE AiffAudio
**** DONE AuAudio
**** DONE WaveAudio
**** DONE decoders.ALACDecoder
**** DONE decoders.FlacDecoder
**** DONE decoders.TTADecoder
**** DONE decoders.WavPackDecoder
*** DONE Add unit tests
** DONE Swap array types for something easier to comprehend
   array_i/array_f/array_u is more opaque than it should be
*** DONE array_i         -> a_int
*** DONE array_i_new()   -> a_int_new()
    - [X] decoders/alac.h/.c
    - [X] decoders/flac.h/.c
    - [X] decoders/mlp.h/.c
    - [X] decoders/oggflac.h/.c
    - [X] decoders/shn.h/.c
    - [X] decoders/tta.h/.c
    - [X] decoders/wavpack.h/.c
    - [X] encoders/alac.h/.c
    - [X] encoders/flac.h/.c
    - [X] encoders/shn.h/.c
    - [X] encoders/tta.h/.c
    - [X] encoders/wavpack.h/.c
    - [X] pcmconverter.h/.c
*** DONE array_ia        -> aa_int
*** DONE array_ia_new()  -> aa_int_new()
    - [X] decoders/alac.h/.c
    - [X] decoders/aob.h/.c
    - [X] decoders/flac.h/.c
    - [X] decoders/mlp.h/.c
    - [X] decoders/oggflac.h/.c
    - [X] decoders/shn.h/.c
    - [X] decoders/sine.h/.c
    - [X] decoders/tta.h/.c
    - [X] decoders/wavpack.h/.c
    - [X] encoders/alac.h/.c
    - [X] encoders/flac.h/.c
    - [X] encoders/shn.h/.c
    - [X] encoders/tta.h/.c
    - [X] encoders/wavpack.h/.c
    - [X] pcmconverter.h/.c
    - [X] replaygain.h/.c
*** DONE array_li        -> l_int
*** DONE array_li_new()  -> l_int_new()
    - [X] encoders/flac.h/.c
*** DONE array_lia       -> al_int
*** DONE array_lia_new() -> al_int_new()
    - [X] encoders/flac.h/.c
    - [X] pcmconverter.h/.c
*** DONE array_u         -> l_unsigned
*** DONE array_u_new     -> l_unsigned_new()
    - [X] decoders/alac.c
    - [X] encoders/alac.c
*** DONE array_lf        -> l_double
*** DONE array_lu        -> l_unsigned
*** DONE array_lu_new()  -> l_unsigned_new()
    - [X] decoders/alac.h/.c
*** DONE array_lfa       -> al_double
*** DONE array_lfa_new() -> al_double_new()
*** DONE array_iaa       -> aaa_int
*** DONE array_iaa_new() -> aaa_int_new()
    - [X] encoders/wavpack.h/.c
    - [X] decoders/wavpach.h/.c
*** DONE array_f         -> a_double
*** DONE array_f_new()   -> a_double_new()
    - [X] pcmconverter.h/.c
    - [X] encoders/alac.h/.c
    - [X] encoders/flac.h/.c
*** DONE array_fa        -> aa_double
*** DONE array_fa_new()  -> aa_double_new()
    - [X] replaygain.h/.c
    - [X] encoders/alac.h/.c
    - [X] encoders/flac.h/.c
*** DONE array_faa       -> aaa_double
*** DONE array_faa_new() -> aaa_double_new()
*** DONE array_o         -> a_obj
*** DONE array_o_new()   -> a_obj_new()
    - [X] decoders/alac.h/.c
    - [X] decoders/aob.h/.c
    - [X] decoders/flac.h/.c
    - [X] encoders/wavpack.h/.c
*** DONE array_i_to_FrameList  -> a_int_to_FrameList
    - [X] decoders/flac.c
    - [X] decoders/oggflac.c
    - [X] pcmconv.c
    - [X] pcmconverter.c
*** DONE array_ia_to_FrameList -> aa_int_to_FrameList
    - [X] decoders/alac.c
    - [X] decoders/aob.c
    - [X] decoders/shn.c
    - [X] decoders/sine.c
    - [X] decoders/tta.c
    - [X] decoders/wavpack.c
    - [X] pcmconv.c
    - [X] pcmconverter.c
    - [X] replaygain.c
** DONE Better document M4A atoms
   For each atom, indicate parent in bit diagram,
   tell what it's for, have a hex diagram example
   and show what those parsed values are.
*** DONE ftyp
*** DONE moov
**** DONE mvhd
**** DONE trak
***** DONE tkhd
***** DONE mdia
****** DONE mdhd
****** DONE hdlr
****** DONE minf
******* DONE smhd
******* DONE dinf
******** DONE dref
******* DONE stbl
******** DONE stsd
******** DONE stts
******** DONE stsc
******** DONE stsz
******** DONE stco
**** DONE udta
***** DONE meta
*** DONE free
*** DONE mdat
*** DONE Add some tree diagrams to fill sections of whitespace
    - [X] moov
    - [X] trak
    - [X] mdia
    - [X] minf
    - [X] stbl
** DONE Add volume control to trackplay/cdplay
*** DONE Add get_volume() method to low-level audio players
    It should return the current set volume as a float.
    - [X] PulseAudioOutput
    - [X] OSSAudioOutput
    - [X] CoreAudioOutput
    - [X] NULLAudioOutput
*** DONE Add set_volume(volume) method to low-level audio players
    It should take a volume setting float and adjust the output volume.
    - [X] PulseAudioOutput
    - [X] OSSAudioOutput
    - [X] CoreAudioOutput
    - [X] NULLAudioOutput
*** DONE Build volume setting, progress-bar style widget
*** DONE Ensure volume control updates when system volume is updated
*** DONE Add widget to player widget
** DONE Make audio output selectable in trackplay/cdplay
*** DONE Add available_outputs() function to audiotools.player module
    should return list of available AudioOutput subclasses
*** DONE Add description() method to AudioOutput subclasses
    Returns a human-readable Unicode string indicating
    what the audio output device is.
    - [X] PulseAudioOutput
    - [X] OSSAudioOutput
    - [X] CoreAudioOutput
    - [X] NULLAudioOutput
*** DONE Ensure output switches correctly
    Switches should be as seamless as possible and include
    setting the output volume properly.
** DONE Add track2track format conversion options
   Primarily for conversion between one lossless format to another.
   - [X] --sample-rate
   - [X] --channels
   - [X] --bits-per-sample
*** DONE Add documentation to man page
*** DONE Add unit tests
** DONE Add internal True Audio codec
*** DONE Add Python-based True Audio decoder
*** DONE Add Python-based True Audio encoder
*** DONE Document True Audio decoding
*** DONE Document True Audio encoding
*** DONE Add C-based True Audio decoder
**** DONE modify it for standalone use
**** DONE ensure there's no memory errors or leaks
**** DONE Make decoder thread-friendly
*** DONE Add C-based True Audio encoder
**** DONE modify it for standalone use
**** DONE ensure there's no memory errors or leaks
**** DONE make encoder thread-friendly
*** DONE Add True Audio-specific unit tests
*** DONE Test encoder/decoder against reference
*** DONE Add True Audio detecter to file_type() function
*** DONE Add True Audio AudioFile class
*** DONE Ensure True Audio class passes all unit tests
** DONE Add AccurateRip support
  This might make give cd2track a little added respectability
  and post-rip tools could be built also to verify tracks/disc images.
*** DONE Add accuraterip Python support
**** DONE add DiscID to accuraterip module
**** DONE add perform_lookup to accuraterip module
**** DONE add accuraterip_lookup to __init__ module
**** DONE add AccurateRipReader to accuraterip module
     This would be a PCMReader wrapper to be used
     by cd2track to verify track checksum while it's being extracted.
**** DONE add AccurateRipTrackChecksum to accuraterip module
     This would be a transfer_framelist_data target
     to be used by trackverify to verify tracks
     already extracted.
**** DONE Add programming docstrings
**** DONE Add programming documentation
*** DONE Add AccurateRip disc ID to cdinfo
*** DONE Add support to cd2track
    This should be done by default as a wrapper around
    the existing CDDA PCMReader objects.
*** DONE Add support to trackverify
    Add a -R/--accuraterip option which foregoes the usual
    calls to .verify(), performs an AccurateRip lookup
    for all albums and indicates whether they match/don't match/aren't found
*** DONE Add protocol documentation
    - [X] calculating the AccurateRip disc ID
    - [X] fetching database entries from the server
    - [X] parsing database entries from the server
    - [X] calculating a track's CRC
    - [X] checking the confidence level
* DONE Finish version 2.21
** DONE Bump minimum required Python version to 2.7
   Python 2.7 is pretty common these days and offers useful
   features that have been backported from 3.
** DONE Cleanup ProgressDisplay design
   Replace row_id indicators with a ProgressDisplayRow object
   which is created by ProgressDisplay and features
   update() and delete() methods that update the main ProgressDisplay.

   This removes the need to specify row_id values and the
   main ProgressDisplay can assign empty slots as needed.
*** DONE Update documentation
** DONE Update temporary file management
   Some of the metadata functions update via tempfile.TemporaryFile
   then overwrite the original once the file's been completed.

   This would be a bad idea in the edge case of a full disk.

   So it may be preferable to write to a temp file alongside the original
   and then rename over it.  That way the original wouldn't be changed
   until commit-time.
   - [X] aiff.py
   - [X] ape.py
   - [X] flac.py
   - [X] m4a.py
   - [X] tta.py
   - [X] wav.py
*** DONE Add unit tests
    Ensure tracks retain permissions when metadata is updated.
** DONE Convert utilties from optparse to argparse
   argparse seems a bit nicer and more user-friendly
   since it generates usage lines automatically.
   - [X] audiotools-config
   - [X] cd2track
   - [X] cdinfo
   - [X] cdplay
   - [X] coverdump
   - [X] covertag
   - [X] coverview
   - [X] dvda2track
   - [X] dvdainfo
   - [X] track2cd
   - [X] track2track
   - [X] trackcat
   - [X] trackcmp
   - [X] trackinfo
   - [X] tracklength
   - [X] tracklint
   - [X] trackplay
   - [X] trackrename
   - [X] tracksplit
   - [X] tracktag
   - [X] trackverify
*** DONE Ensure all utilities have a working --version option
    Now that it's no longer built-in, I'll need to add and check
    the option by hand.
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] covertag
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
** DONE Improve Ogg format metadata speeds
   Reading/writing should be faster for all Ogg-based formats.
*** DONE Ogg FLAC
*** DONE Ogg Vorbis
*** DONE Opus
** DONE Adjust MetaData.clean() interface
   Returns (MetaData, [fixes]) tuple
   where MetaData is a new, fixed metadata object
   and fixes is a list of Unicode strings.
*** DONE Fix MetaData objects
    - [X] MetaData
    - [X] APEv2
    - [X] FlacMetaData
    - [X] ID3v22Comment
    - [X] ID3v23Comment
    - [X] ID3v24Comment
    - [X] ID3CommentPair
    - [X] ID3v1Comment
    - [X] M4A_META_Atom
    - [X] VorbisComment
*** DONE Update documentation
*** DONE Update unit tests
** DONE Adjust AudioFile.clean() interface
   Takes optional output_filename and returns list of fixes performed.
   It would be nice not to need separate code paths
   for dry runs, like with MetaData, but that may not be feasible.
*** DONE Fix AudioFile objects
    - [X] AudioFile
    - [X] AiffAudio
    - [X] FlacAudio
    - [X] WaveAudio
*** DONE Update tracklint
*** DONE Update documentation
*** DONE Update unit tests
** DONE Add lint for "double-wrapped" ID3v2 tags
   Sometimes files are idiotically wrapped in multiple layers of ID3v2 tags
   like nested Russian dolls.  These are currently unrecognized because
   audiotools assumes a valid track follows the tag instead of another tag.
*** DONE Support tracks nested arbitrarily deep in ID3v2 tags
    In order to be fixable,
    these files will need to be readable in the first place
**** DONE Update file_type function to skip ID3v2 tags recursively
     It should still only find MP2/MP3/FLAC/TTA files how matter how
     deeply nested, but should support arbitrarily deep nesting.
**** DONE Update FlacAudio
***** DONE Update FlacAudio to support nested tags
      This is basically a matter of making the offset as large
      as it takes to hold all the tags.
***** DONE Have FlacAudio.clean() remove all ID3v2 tags
***** DONE Update unit tests
**** DONE Update MP3Audio
***** DONE Update MP3Audio to support nested tags
***** DONE Have MP3Audio.clean() remove all ID3v2 tags except one
***** DONE Update unit tests
**** DONE Update MP2Audio
***** DONE Update MP2Audio to support nested tags
***** DONE Have MP2Audio.clean() remove all ID3v2 tags except one
***** DONE Update unit tests
**** DONE Update TrueAudio
***** DONE Update TrueAudio to support nested tags
***** DONE Have TrueAudio.clean() remove all ID3v2 tags except one
***** DONE Update unit tests
* DONE Finish version 2.22
** DONE Update ReplayGain interface
   All ReplayGain should be lossless (if supported at all) and needs an interface
   that's less dependant on external tools.
*** DONE Remove add_replay_gain() classmethod
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Add add_replay_gain() function
    Takes an album's worth of audiofile objects and optional progress function
    and if any of their classes support it (via the supports_replay_gain() classmethod)
    calls calculate_replay_gain() on all of them and applies the result
    using set_replay_gain()
*** DONE Remove can_add_replay_gain() classmethod
    If supports_replay_gain() classmethod returns True,
    set_replay_gain() will be able to do something useful.
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Remove lossless_replay_gain classmethod
    All ReplayGain will be lossless
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Convert replay_gain() method to get_replay_gain()
    Returns a ReplayGain object or None, as it does now.
    Analagous to get_metadata()
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Add set_replay_gain() method
    Takes a ReplayGain object or None and sets the file's values
    by updating metadata, or deletes the values if None
    Analagous to set_metadata()
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Add delete_replay_gain() method
    Deletes any ReplayGain values, if any
    Analagous to delete_metadata()
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Update utilities to use new ReplayGain interface
    - [X] cd2track
    - [X] dvda2track
    - [X] track2track
    - [X] trackinfo
    - [X] trackplay
    - [X] tracksplit
    - [X] tracktag
*** DONE Update documentation
*** DONE Update unit tests
** DONE Catch interrupts more cleanly in utilities
   When the user cancels a long-running job, utilities should clean the screen
   as well as possible, delete any partial files and display a "cancelled" message
   instead of delivering stack traces like they do now.
   - [X] cd2track
   - [X] cdplay
   - [X] dvda2track
   - [X] track2cd
   - [X] track2track
   - [X] trackcat
   - [X] trackcmp
   - [X] tracklength
   - [X] trackplay
   - [X] tracksplit
   - [X] tracktag
   - [X] trackverify
** DONE Add AudioFile.seekable() method
   This returns True if the format's PCMReader has a .seek() method
   and that method supports some sort of fine-grained seeking
   (i.e. not just return to the beginning of the file).
*** DONE Update audio formats
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OggFlacAudio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Update documentation
*** DONE Update utilities
**** DONE tracksplit
     If the file being split isn't seekable, decompress it to a temp file
     so it can be processed across multiple jobs.
**** DONE trackcmp
     If comparing multiple tracks against a non-seekable image,
     decompress it to a temp file so it can be processed across multiple jobs.
*** DONE Update unit tests
** DONE Have AccurateRip detect offset tracks
   If the tracks have been ripped accurately but are merely offset
   by some small positive or negative number of samples,
   this should be detected and indicated in the AccurateRip results screens.
*** DONE Update ChecksumV1
**** DONE Add keyword parameters with sensible defaults
**** DONE Add range option
     This indicates the total range of the window size.
     Defaults to 1, indicating only a single checksum to be generated.
**** DONE Change .checksum() method to .checksums()
     Returns a list of all calculated checksums over the whole range.
*** DONE Add unit tests
    Ensure ranged and non-ranged checksums are calculated properly
    and update tests for new interface.
*** DONE Build audiotools.cdio.CDDAReader object
    This functions like a PCMReader object over the entire CDDA device.
    It should work transparently on physical CD devices as well as CD images
    and be PCM-frame centric by doing all the CD sector conversions
    behind-the-scenes.
**** DONE Add methods and attributes
***** DONE CDDAReader.sample_rate
***** DONE CDDAReader.channels
***** DONE CDDAReader.channel_mask
***** DONE CDDAReader.bits_per_sample
***** DONE CDDAReader.track_offsets
      - [X] CD image
      - [X] CD drive
      Returns starting offset of each track, in PCM frames
***** DONE CDDAReader.track_lengths
      - [X] CD image
      - [X] CD drive
      Returns length of each track, in PCM frames
***** DONE CDDAReader.first_sector
      - [X] CD image
      - [X] CD drive
      Used for calculating various disc IDs
***** DONE CDDAReader.last_sector
      - [X] CD image
      - [X] CD drive
      Used for calculating various disc IDs
***** DONE CDDAReader.is_image
      - [X] CD drive
      - [X] CD image
***** DONE CDDAReader.read(pcm_frames)
      - [X] CD image
      - [X] CD drive
***** DONE CDDAReader.seek(pcm_frames)
      - [X] CD image
      - [X] CD drive
***** DONE CDDAReader.set_speed(speed)
      - [X] CD image
      - [X] CD drive
***** DONE CDDAReader.log()
      - [X] CD image
      - [X] CD drive
***** DONE CDDAReader.reset_log()
***** DONE CDDAReader.close()
**** DONE Update documentation
**** DONE Update unit tests
**** DONE Detect nonexistent discs
     CDDAReader should detect when a physical drive doesn't contain a disc
     and raise an appropriate exception.
**** DONE Update utilities
***** DONE cd2track
***** DONE cdinfo
***** DONE cdplay
**** DONE Clean out old audiotools.CDDA object
     Remove outdated interface, its documentation and unit tests.
*** DONE Update utilities
    When verifying AccurateRip, make sure to populate the checksum
    with the last few samples of the previous track (if any)
    and the next few samples of the next track (if any)
    in order to find the checksum's offset.
**** DONE trackverify
**** DONE cd2track
** DONE Simplify lookup services interface
   The perform_lookup() functions should work on DiscID objects
   and DiscID objects should be generated from
   CDDAReaders, track lists or cuesheet/length combinations.
*** DONE FreeDB
    - [X] Add from_tracks classmethod
    - [X] Add from_sheet classmethod
    - [X] Add unit tests
*** DONE MusicBrainz
    - [X] Add from_tracks classmethod
    - [X] Add from_sheet classmethod
    - [X] Add unit tests
*** DONE AccurateRip
    - [X] Add from_tracks classmethod
    - [X] Add from_sheet classmethod
    - [X] Add unit tests
** DONE Add documentation for lookup services
   - [X] audiotools.freedb
   - [X] audiotools.musicbrainz
   - [X] audiotools.accuraterip
** DONE Convert TOC and CUE handling to proper grammars
*** DONE Convert audiotools.cue
*** DONE Convert audiotools.toc
** DONE Add AccurateRipV2 support
*** DONE Add ChecksumV2 object
**** DONE Add unit tests
*** DONE Update utilities
    - [X] cd2track
    - [X] trackverify
** DONE Update Sheet interface
   Although cuesheets are mostly a repository of index points,
   it would be useful to support a wider subset of options
   when converting one form to another.
*** DONE Sheet
    - [X] @classmethod converted(sheet) -> Sheet
    builds Sheet object from Sheet-compatible object
    - [X] __len__() -> total_tracks
    - [X] __getitem__(track_index) -> SheetTrack
    - [X] __eq__(sheet) -> boolean
    - [X] track_numbers() -> [int]
    - [X] track(track_number) -> SheetTrack
    - [X] get_metadata() -> MetaData
    MetaData contains catalog_number and CD-TEXT information
*** DONE SheetTrack
    - [X] @classmethod converted(sheet_track) -> SheetTrack
    builds SheetTrack object from SheetTrack-compatible object
    - [X] __len__() -> total_indexes
    - [X] __getitem__(index_index) -> SheetIndex
    - [X] __eq__(sheet_track) -> boolean
    - [X] indexes() -> [int]
    - [X] index(index_number) -> SheetIndex
    - [X] filename() -> string
    - [X] number() -> track_number
    - [X] get_metadata() -> MetaData
    MetaData contains track_number, ISRC and CD-TEXT information
    - [X] is_audio() -> boolean
    - [X] pre_emphasis() -> boolean
    - [X] copy_peritted() -> boolean
*** DONE SheetIndex
    - [X] @classmethod converted(sheet_index) -> SheetIndex
    - [X] __eq__(sheet_index) -> boolean
    - [X] number() -> index_number
    pre-gap is index_number 0
    - [X] offset() -> Fraction
    offset of index in seconds from start of stream
*** DONE Update audiotools.cue.Cuesheet
*** DONE Update audiotools.toc.TOCFile
*** DONE Update audiotools.flac.Flac_CUESHEET
*** DONE Ensure non-image cuesheets are handled properly
    Given a non-image cuesheet and list of track lengths,
    it may be possible to convert a non-image cuesheet
    to an image cuesheet.
*** DONE Handle 1st track pre-gap correctly
    Discs with a pre-gap before the 1st track
    should have that gap removed when split
    and re-added when concatenating them back together
    based on the cuesheet contents.
    - [X] update tracksplit
    - [X] update trackcat
    - [X] add unit tests
*** DONE Update utilities
    - [X] trackcat
    - [X] tracksplit
    - [X] track2cd
    - [X] trackinfo
*** DONE Update functions/methods which take cuesheets
    - [X] sheet_metadata_lookup
    - [X] accuraterip_sheet_lookup
*** DONE Update documentation
*** DONE Update unit tests
** DONE Converts AudioFile.seconds_length() method to Fraction
   This should be more useful than the Decmial it was using before.
*** DONE Update method
*** DONE Update documentation
*** DONE Update utilities
    - [X] track2cd
    - [X] trackcat
    - [X] trackinfo
    - [X] tracklength
    - [X] trackplay
    - [X] tracksplit
* DONE Finish version 3.0
** DONE Make compatible with Python 2.7 and 3.X
   Like Urwid and PLY, the same code should work on both versions.
*** DONE Update setup script
*** DONE Update C-based extensions
    Use #ifdefs as needed to support both Python versions.

    - [X] audiotools.pcm
    - [X] audiotools.pcmconverter
    - [X] audiotools.replaygain
    - [X] audiotools.decoders
    - [X] audiotools.encoders
    - [X] audiotools.bitstream
    - [X] audiotools._ogg
    - [X] audiotools._accuraterip
    - [X] audiotools.output
*** DONE Update Python modules
**** DONE audiotools
     - [X] __init__
     - [X] accuraterip
     - [X] aiff
     - [X] ape
     - [X] au
     - [X] dvda
     - [X] flac
     - [X] freedb
     - [X] id3
     - [X] id3v1
     - [X] image
     - [X] m4a_atoms
     - [X] m4a
     - [X] mp3
     - [X] musicbrainz
     - [X] ogg
     - [X] opus
     - [X] player
     - [X] shn
     - [X] text
     - [X] tta
     - [X] ui
     - [X] vorbiscomment
     - [X] vorbis
     - [X] wavpack
     - [X] wav
**** DONE audiotools.cue
     - [X] __init__
     - [X] tokrules
     - [X] yaccrules
**** DONE audiotools.toc
     - [X] __init__
     - [X] tokrules
     - [X] yaccrules
*** DONE Update utilities
    - [X] audiotools-config
    - [X] cd2track
    - [X] cdinfo
    - [X] cdplay
    - [X] coverdump
    - [X] covertag
    - [X] coverview
    - [X] dvda2track
    - [X] dvdainfo
    - [X] track2cd
    - [X] track2track
    - [X] trackcat
    - [X] trackcmp
    - [X] trackinfo
    - [X] tracklength
    - [X] tracklint
    - [X] trackplay
    - [X] trackrename
    - [X] tracksplit
    - [X] tracktag
    - [X] trackverify
*** DONE Update Python-based decoders
    - [X] alac
    - [X] flac
    - [X] shn
    - [X] tta
    - [X] wavpack
*** DONE Update Python-based encoders
    - [X] alac
    - [X] flac
    - [X] shn
    - [X] tta
    - [X] wavpack
*** DONE Update documentation
*** DONE Update unit tests
    - [X] test
    - [X] test_core
    - [X] test_formats
    - [X] test_metadata
    - [X] test_streams
    - [X] test_utils
**** TODO Update unit test utilities
     - [ ] error.py
     - [ ] test_cdrdao
     - [ ] test_cdrecord
*** DONE Ensure unit tests pass
**** DONE Python 2.7
***** DONE Lib
      - [X] core
      - [X] cuesheet
      - [X] cdio
      - [X] freedb
      - [X] image
      - [X] musicbrainz
      - [X] accuraterip
      - [X] pcm
      - [X] bitstream
      - [X] replaygain
      - [X] resample
      - [X] tocfile
      - [X] verify
      - [X] ogg
***** DONE Format
      - [X] audiofile
      - [X] lossless
      - [X] lossy
      - [X] aiff
      - [X] alac
      - [X] au
      - [X] dvda
      - [X] flac
      - [X] m4a
      - [X] mp2
      - [X] mp3
      - [X] oggflac
      - [X] opus
      - [X] shorten
      - [X] sines
      - [X] tta
      - [X] vorbis
      - [X] wave
      - [X] wavpack
***** DONE Metadata
      - [X] metadata
      - [X] flac
      - [X] wavpack
      - [X] id3v1
      - [X] id3v2
      - [X] vorbis
      - [X] opus
      - [X] m4a
      - [X] tta
***** DONE Util
      - [X] audiotools-config
      - [X] cd2track
      - [ ] cdinfo
      - [X] cdplay
      - [X] coverdump
      - [X] covertag
      - [X] coverview
      - [X] dvda2track
      - [X] dvdainfo
      - [X] track2cd
      - [X] track2track
      - [X] trackcat
      - [X] trackcmp
      - [X] trackinfo
      - [X] tracklength
      - [X] tracklint
      - [X] trackplay
      - [X] trackrename
      - [X] tracksplit
      - [X] tracktag
      - [X] trackverify
**** DONE Python 3
***** DONE Lib
      - [X] core
      - [X] cuesheet
      - [X] cdio
      - [X] freedb
      - [X] image
      - [X] musicbrainz
      - [X] accuraterip
      - [X] pcm
      - [X] bitstream
      - [X] replaygain
      - [X] resample
      - [X] tocfile
      - [X] verify
      - [X] ogg
***** DONE Format
      - [X] audiofile
      - [X] lossless
      - [X] lossy
      - [X] aiff
      - [X] alac
      - [X] au
      - [X] dvda
      - [X] flac
      - [X] m4a
      - [X] mp2
      - [X] mp3
      - [X] oggflac
      - [X] opus
      - [X] shorten
      - [X] sines
      - [X] tta
      - [X] vorbis
      - [X] wave
      - [X] wavpack
***** DONE Metadata
      - [X] metadata
      - [X] flac
      - [X] wavpack
      - [X] id3v1
      - [X] id3v2
      - [X] vorbis
      - [X] opus
      - [X] m4a
      - [X] tta
***** DONE Util
      - [X] audiotools-config
      - [X] cd2track
      - [X] cdinfo
      - [X] cdplay
      - [X] coverdump
      - [X] covertag
      - [X] coverview
      - [X] dvda2track
      - [X] dvdainfo
      - [X] track2cd
      - [X] track2track
      - [X] trackcat
      - [X] trackcmp
      - [X] trackinfo
      - [X] tracklength
      - [X] tracklint
      - [X] trackplay
      - [X] trackrename
      - [X] tracksplit
      - [X] tracktag
      - [X] trackverify
** DONE Update interactive utilities
   Ensure they run under Python 2 and 3
   - [X] audiotools/ui.py
   - [X] audiotools-config
   - [X] cd2track
   - [X] cdplay
   - [X] dvda2track
   - [X] track2track
   - [X] trackcat
   - [X] trackplay
   - [X] trackrename
   - [X] tracksplit
   - [X] tracktag
** DONE Calculate ReplayGain at rip-time
   For utilities that operated single-threaded,
   calculate ReplayGain during operation rather
   than put off until ripping is done.
*** DONE cd2track
*** DONE dvda2track
** DONE Re-add DVD-A support using libdvdaudio
   Supporting DVD-A in an external library should provide a useful decoupling.
*** DONE Add audiotools.dvda module
    This contains thin wrappers around libdvdaudio structures and functions.
**** DONE Add DVDA object
**** DONE Add Titleset object
**** DONE Add Title object
**** DONE Add Track object
**** DONE Add TrackReader object
**** DONE Add documentation
*** DONE Re-add dvda2track script
**** DONE Re-add dvda2track.1 man page
*** DONE Re-add dvdainfo script
**** DONE Re-add dvdainfo.1 man page
*** DONE Update setup script
    Probe for libdvdaudio and add module and scripts if present,
    analagous to libcdio.
* DONE Finish version 3.1
** DONE Make AudioFile.available() classmethod finer-grained
   That is, formats can be readable but not writable, or taggable but
   not readable or writeable.  This will allow read-only support for
   some obscure formats like .tak or .ape by porting implementations
   from ffmpeg but without having to build encoders for them as well.
*** DONE Add AudioFile.supports_to_pcm()
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Add AudioFile.supports_from_pcm()
    - [X] AudioFile
    - [X] ALACAudio
    - [X] AiffAudio
    - [X] AuAudio
    - [X] FlacAudio
    - [X] M4AAudio
    - [X] MP2Audio
    - [X] MP3Audio
    - [X] OpusAudio
    - [X] ShortenAudio
    - [X] TrueAudio
    - [X] VorbisAudio
    - [X] WavPackAudio
    - [X] WaveAudio
*** DONE Add AudioFile.supports_metadata()
*** DONE Remove AudioFile.missing_components() ?
    Leave this job to setup.py and audiotools-config, perhaps.
*** DONE Utilities
**** DONE audiotools-config
     Needs to display format availability in a more fine-grained fashion.
**** DONE cd2track
     - [X] output format must supports_from_pcm()
**** DONE track2cd
     - [X] input format must supports_to_pcm()
**** DONE track2track
     - [X] input format(s) must supports_to_pcm()
     - [X] output format must supports_from_pcm()
**** DONE trackcat
     - [X] input format(s) must supports_to_pcm()
     - [X] output format must supports_from_pcm()
**** DONE trackcmp
     - [X] input format(s) must supports_to_pcm()
**** DONE trackplay
     - [X] input format(s) must supports_to_pcm()
**** DONE tracksplit
     - [X] input file must supports_to_pcm()
     - [X] output format must supports_from_pcm()
**** DONE trackverify
     - [X] input format must supports_to_pcm()
*** DONE Update documentation
*** DONE Update unit tests
* DONE Add compilation field to metadata
  This boolean field indicates the track is part of a compilation
  and should round-trip correctly across all metadata types
  that support it.
** DONE Replace MetaData.INTEGER_FIELDS with a FIELD_TYPE lookup
   This allows a field to be either a Unicode string (like track_name)
   integer field (like track_number) or a boolean (like compilation).
** DONE Add field to metadata classes
*** DONE Add compilation field to MetaData class
*** DONE Add compilation field to ID3v2 classes
    - [X] ID3v2.2
    - [X] ID3v2.3
    - [X] ID3v2.4
    - [X] ID3CommentPair
*** DONE Add compilation field to VorbisComment classes
    - [X] VorbisComment
    - [X] FlacMetaData
*** DONE Add compilation field to M4A metadata
*** DONE Add compilation field to APEv2 metadata
** DONE Update tracktag to support compilation field
   - [X] add command-line options
   - [X] add UI support
*** TODO Update man page
** DONE Update documentation
** DONE Update unit tests
   - [X] format unit tests
   - [X] tracktag unit tests
* TODO Add support for genre tag?
  I'm not a big fan of the genre tag.
  Unlike track number, album name, ISRC, etc. in which a value
  can be reliably determined from the source material (e.g. back of the CD),
  genre is akin to a "rating" tag. Its value varies from person to person
  and this makes it less valuable for archival purposes.

  In addition, the genre tag itself is implemented in incompatible ways.
  APEv2 uses a chunk of text, ID3v1 uses an integer representing one
  of many designated genre labels, ID3v2 uses a mix of genre byte
  and/or text string, and so on.

  That said, the genre field shows up in a lot of players.
  So, some grudging support for it would probably be appreciated.
* TODO Replace magic numbers with named constants
  There's still a few instances of magic numbers in use,
  in the __flac__.py module, for instance.
* TODO Support disc metadata submission
  If a disc is not found on FreeDB or MusicBrainz,
  there should be some mechanism to submit user-defined data
  to those services.
* TODO build track2cover utility
  It would be helpful to have tools that could query
  online cover databases and retrieve images for tagging purposes.
  track2cover can replace coverdump, functioning like trac2xmcd
  which allows it to pull from existing metadata or from online sources.
* TODO build trackverify utility
  Much like flac(1)'s --verify option, this should check
  a file for correctness and, if possible, verify its output
  matches any internal hashes/checksums.
  May work recursively to check a user's entire collection.
** DONE add a .verify() method to AudioFile classes
   Returns True if the file is okay.  Raises InvalidFile if not.
   Include unit test.
   - [X] AiffAudio
   - [X] ALACAudio
   - [X] AuAudio
   - [X] FlacAudio
   - [X] M4AAudio
   - [X] MP2Audio
   - [X] MP3Audio
   - [X] OggFlacAudio
   - [X] SpeexAudio
   - [X] ShortenAudio
   - [X] VorbisAudio
   - [X] WaveAudio
   - [X] WavPackAudio
** TODO add trackverify unit tests for all known verifiable problems
** DONE add trackverify man page
** DONE link trackverify man page to others
* TODO Add a -r/--no-results flag to trackcmp/trackverify
  Listing only the final results and not the running list
** TODO Update man page
* TODO Add DVD-A support
** DONE Build AOB parser/extractor
*** DONE Optimize AOB extractor for better offset handling
    The current version works, but is a little too naive
** TODO Build CPPM handler
*** DONE Create audiotools.prot module
*** TODO Create audiotools.prot.CPPMDecoder object
**** DONE Add CPPMDecoder.__init__() method
**** DONE Add CPPMDecoder.decode() method
**** TODO Ensure CPPMDecoder is cross-platform
** DONE Document AOB parser
** DONE Build raw PCM decoder
   The PCM data doesn't appear to be stored raw,
   so some additional calculation will likely be necessary
** TODO Build MLP decoder
*** DONE Create decoders.MLPDecoder class
**** DONE Add MLPDecoder.init() method
**** DONE Add MLPDecoder.sample_rate attribute
**** DONE Add MLPDecoder.channels attribute
**** DONE Add MLPDecoder.bits_per_sample attribute
**** DONE Add MLPDecoder.channel_mask attribute
**** DONE Add MLPDecoder.read() method
**** DONE Add MLPDecoder.analyze_frame() method
**** DONE Add MLPDecoder.close() method
*** DONE Perform MLP frame parsing
**** DONE Perform frame size parsing
**** DONE Perform major sync parsing
**** DONE Perform substream sizes parsing
**** DONE Perform substream datas parsing
***** DONE Perform block parsing
****** DONE Perform restart header parsing
****** DONE Perform decoding parameters parsing
******* DONE Perform parameter presence flags parsing
******* DONE Perform block size parsing
******* DONE Perform matrix parameters parsing
******* DONE Perform output shifts parsing
******* DONE Perform quant step sizes parsing
******* DONE Perform channel parameters parsing
******** DONE Perform FIR filter parameters parsing
******** DONE Perform IIR filter parameters parsing
******** DONE Perform Huffman offset parsing
******** DONE Perform Codebook parsing
******** DONE Perform Huffman least-significant bits parsing
****** DONE Perform block data parsing
***** DONE Perform block alignment
***** DONE Perform CRC parsing
*** TODO Perform MLP data decoding
**** DONE Handle FIR/IIR filtering
**** DONE Handle matrix reconstruction
***** DONE Build noise channels
**** DONE Handle quant_step_sizes
**** DONE Combine multiple substreams into single output frame
**** DONE Handle output shifts
**** TODO Handle checksums
***** TODO restart header lossless check
***** DONE end-of-frame parity
***** DONE end-of-frame checksum
**** DONE Build pcm.FrameList from PCM data arrays
***** DONE Reorder MLP channels to wave order
**** DONE Add bs_try for proper EOF aborts
**** DONE Add end-of-stream marker checking to read()
**** TODO Add end-of-stream marker checking to analyze_frame()
**** DONE Optimize to work faster
     Perhaps with multiple blocks per call to read()
**** TODO Support 1 substream output
     MLP decoding supports 1 or 2 substreams.
     In some cases, substream 1 will contain the first 2 channels
     while substream 2 will contain the rest,
     which provides two different "mixes" depending on whether
     one is decoding to 2 channels or not.
***** TODO Update dvda2track with 1 substream output option
      This would allow the user to decide which "mix" is desired
      during extraction.
*** DONE Perform sanity checks
** TODO Document MLP decoder
*** DONE major sync
*** DONE substream size
*** DONE substream data
*** DONE restart header
*** DONE decoding parameters
**** DONE parameter presence flags
**** DONE block size
**** DONE matrix parameters
**** DONE output shifts
**** DONE quant step sizes
*** DONE checksums
*** DONE block data
*** DONE channel filtering
*** DONE channel rematrixing
**** DONE noise channels
**** DONE rematrixing
*** DONE end of stream marker
*** TODO decoding XOR byte
** DONE Document PCM decoder
** DONE Link MLP decoder to AOB extractor
** DONE Link PCM decoder to AOB extractor
** TODO Build DVD-A postprocessor
** DONE Document Python interface
** DONE Build dvdainfo utility
*** DONE Add dvdainfo.1 man page
*** DONE Ensure -A with invalid dir generates error message
** DONE Build dvda2track utility
*** DONE Fix to work without -d
*** DONE Add --track-start / --track-total options
    Since some especially long discs split tracks across titles,
    it makes sense to have a way to specify the starting track number
    and the total number of tracks.
**** DONE Add options to man page
*** DONE Add dvda2track.1 man page
*** DONE Ensure -A with invalid dir generates error message
** DONE Build dvda2xmcd utility
   Querying FreeDB and MusicBrainz are possible,
   but I expect most of the time they'll generate empty templates.
*** DONE Add dvda2xmcd.1 man page
*** DONE Ensure -A with invalid dir generates error message
* TODO Add cdplay utility
  Should work similarly to trackplay,
  taking track number(s) and playing them to OSS/PulseAudio
  or whatever other audio output is available.
** DONE Add audiotools.player.CDPlayer class
*** DONE Add docstrings
*** DONE Add programming docs
** DONE Add audiotools.player.CDPlayerThread class
*** DONE Add docstrings
** DONE Add non-interactive mode to cdplay
** DONE Add interative mode to cdplay
   Urwid-based display should have various standard features
   - [X] display CD info
   - [X] progress monitor
   - [X] start
   - [X] pause
   - [X] next track
   - [X] previous track
** DONE Silence CD query messages
** DONE Support command-line tracks
   One might want to play only a subset of the whole CD
** DONE Add -V/--verbose option
** DONE Add --shuffle option
** DONE Add -x/--xmcd file support
** DONE Avoid spinning down CDs between tracks
   This makes playback less seamless than I'd like
** TODO Improve stability
** DONE Add man page for cdplay.1
** TODO Add basic unit tests
   - [ ] Check command-line arguments
* TODO Reorganize test suite
  The current haphazard layout makes it too difficult to deterimine
  if some feature has been fully unit tested.
  Or, if a new feature is added, the logical place to put new tests
  is not obvious.
** TODO Group tests for the audio formats
*** DONE AudioFile
    - [X] test_init
    - [X] test_is_type
    - [X] test_bits_per_sample
    - [X] test_metadata
    - [X] test_length
    - [X] test_track_number
    - [X] test_album_number
    - [X] test_track_name
    - [X] test_replay_gain
*** DONE LosslessAudioFile
    - [X] test_lossless
    - [X] test_channels
    - [X] test_channel_mask
    - [X] test_sample_rate
    - [X] test_pcm
    - [X] test_convert
*** DONE LossyAudioFile
    - [X] test_bits_per_sample
    - [X] test_lossless
    - [X] test_channels
    - [X] test_channel_mask
    - [X] test_sample_rate
    - [X] test_pcm
    - [X] test_convert
*** DONE ALACAudio
    - [X] test_init
    - [X] test_bits_per_sample
    - [X] test_channel_mask
    - [X] test_verify
    - [X] test_streams
    - [X] test_small_files
    - [X] test_full_scale_deflection
    - [X] test_sines
    - [X] test_wasted_bps
    - [X] test_blocksizes
    - [X] test_noise
    - [X] test_fractional
    - [X] test_frame_header_variations
*** TODO AiffAudio
    - [ ] test_init
    - [X] test_channel_mask
    - [X] test_verify
    - [X] test_roundtrip_aiff_chunks
    - [X] test_convert_aiff_chunks
*** TODO AuAudio
    - [ ] test_init
    - [X] test_verify
    - [X] test_channel_mask
*** TODO FlacAudio
    - [ ] test_init
    - [ ] test_metadata2
    - [X] test_verify
    - [ ] test_cuesheet
    - [X] test_roundtrip_aiff_chunks
    - [X] test_convert_aiff_chunks
    - [X] test_roundtrip_wave_chunks
    - [X] test_convert_wave_chunks
    - [X] test_streams
    - [X] test_small_files
    - [X] test_full_scale_deflection
    - [X] test_sines
    - [X] test_wasted_bps
    - [X] test_blocksizes
    - [X] test_frame_header_variations
    - [X] test_option_variations
    - [X] test_noise
    - [X] test_fractional
*** TODO M4AAudio
    - [ ] test_init
    - [ ] test_verify
*** TODO MP2Audio
    - [ ] test_init
    - [X] test_length
    - [ ] test_verify
*** TODO MP3Audio
    - [ ] test_init
    - [X] test_length
    - [ ] test_verify
*** TODO OggFlacAudio
    - [ ] test_init
    - [X] test_verify
    - [ ] test_cuesheet
*** TODO ShortenAudio
    - [ ] test_init
    - [X] test_bits_per_sample
    - [X] test_verify
    - [X] test_roundtrip_wave_chunks
    - [X] test_convert_wave_chunks
    - [X] test_streams
    - [X] test_small_files
    - [X] test_full_scale_deflection
    - [X] test_sines
    - [X] test_blocksizes
    - [X] test_noise
*** TODO VorbisAudio
    - [ ] test_init
    - [X] test_channels
    - [X] test_verify
*** TODO WavPackAudio
    - [ ] test_init
    - [X] test_verify
    - [ ] test_cuesheet
    - [X] test_roundtrip_wave_chunks
    - [X] test_convert_wave_chunks
    - [X] test_small_files
    - [X] test_full_scale_deflection
    - [X] test_wasted_bps
    - [X] test_blocksizes
    - [X] test_silence
    - [X] test_noise
    - [X] test_fractional
    - [X] test_multichannel
    - [X] test_sines
    - [X] test_option_variations
*** TODO WaveAudio
    - [ ] test_init
    - [X] test_verify
    - [X] test_roundtrip_wave_chunks
    - [X] test_convert_wave_chunks
** DONE Group tests for the metadata formats
*** DONE ApeTag
*** DONE FLAC
*** DONE ID3v1
*** DONE ID3v2.2
*** DONE ID3v2.3
*** DONE ID3v2.4
*** DONE IDCommentPair
*** DONE M4A
*** DONE VorbisComment
*** DONE Ensure that merge() works properly
      It has to handle all supported text fields
      *and* must handle images if supported.
** TODO Group tests for the core libraries
*** TODO audiotools.__init__
**** TODO AlbumMetaData
**** DONE BufferedPCMReader
**** DONE CDDA
**** DONE ChannelMask
**** DONE Image
**** DONE LimitedPCMReader
**** TODO Messenger
**** DONE PCMCat
**** DONE PCMConverter
**** DONE PCMReaderWindow
**** TODO ReorderedPCMReader
**** DONE ReplayGain
**** DONE applicable_replay_gain
**** DONE at_a_time
**** DONE build_timestamp
**** DONE calculate_replay_gain
**** DONE filename_to_type
**** DONE group_tracks
**** TODO make_dirs
**** DONE open
**** DONE open_directory
**** DONE open_files
**** DONE parse_timestamp
**** DONE pcm_frame_cmp
**** DONE pcm_split
**** DONE read_sheet
**** DONE str_width
**** DONE transfer_framelist_data
**** DONE XMCD
**** DONE MusicBrainzReleaseXML
*** DONE audiotools.pcm
**** DONE FloatFrameList
**** DONE FrameList
**** DONE from_channels
**** DONE from_float_Frames
**** DONE from_float_channels
**** DONE from_frames
**** DONE from_list
*** DONE audiotools.player
**** DONE Player
**** DONE CDPlayer
*** DONE audiotools.replaygain
**** DONE ReplayGain
**** DONE ReplayGainReader
*** DONE audiotools.decoders
**** DONE BitstreamReader
*** DONE audiotools.encoders
**** DONE BitstreamWriter
*** TODO audiotools.verify
**** TODO mpeg
**** TODO ogg
** TODO Group tests for the individual tools
*** TODO audiotools-config
**** TODO test_options
**** TODO test_errors
*** DONE cd2track
**** DONE test_options
**** DONE test_errors
*** DONE cd2xmcd
**** DONE test_options
**** DONE test_errors
*** TODO cdinfo
**** TODO test_options
**** TODO test_errors
*** TODO cdplay
**** TODO test_options
**** TODO test_errors
*** DONE coverdump
**** DONE test_options
**** DONE test_errors
*** DONE dvdainfo
**** DONE test_errors
*** DONE dvda2track
**** DONE test_errors
*** DONE dvda2xmcd
**** DONE test_errors
*** DONE track2track
**** DONE test_options
**** DONE test_errors
*** DONE track2xmcd
**** DONE test_options
**** DONE test_errors
*** DONE trackcat
**** DONE test_options
*** DONE trackcmp
**** DONE test_options
*** TODO trackinfo
**** TODO test_options
**** TODO test_errors
*** DONE tracklength
*** DONE tracklint
*** TODO trackplay
**** TODO test_options
**** TODO test_errors
*** DONE trackrename
**** DONE test_options
**** DONE test_errors
*** DONE tracksplit
**** DONE test_options
**** DONE test_errors
*** TODO tracktag
**** DONE test_options
**** TODO test_errors
*** TODO trackverify
**** TODO test_options
**** TODO test_errors
** DONE Split tests into multiple files
* TODO Handle write errors more gracefully
  Just as the BitstreamReader uses an exception mechanism to detect
  and handle read errors, the BitstreamWriter should use a similar
  mechanism to detect write errors and bubble-up a Python exception.

  The problem is that invalid writes are difficult to unit test.
  With a reader, one can simply truncate a valid file.
  It's harder to constrict a writer to only have X number of bytes
  available so that any errors can be checked.
** TODO Add try/etry support to BitstreamWriter
** DONE Update BitstreamWriter methods to raise exceptions
** TODO Update Python-based BitstreamWriter to catch C exceptions
** TODO Update encoders to catch C exceptions and raise Python exceptions
** TODO Unit test write errors
* TODO Verify metadata against reference implementations
  Where feasible, try to ensure our metadata
  matches what's read/written by known reference implementations.
** TODO FlacAudio
   against metaflac
** TODO M4AAudio/ALACAudio
   against taglib?
** TODO MP3Audio/MP2Audio
   Against taglib, perhaps?  Venerable id3lib doesn't work with UCS-2.
** TODO VorbisAudio
   against vorbis-tools
** TODO WavPackAudio
   against tablib?
* TODO Add lyrics metadata support
  Supporting storing lyrics text in the audio files themselves
  would be quite useful.
** TODO Add .lyrics field to MetaData
   Analagous to the lengthy .comment field, most likely.
** TODO Add unit testing for .lyrics field
** TODO Find automated source for lyrics
   Analagous to CDDB.
   This will be challenging since online sources for lyrics are rare.
   Unfortunately, expecting everyone to transcribe lyrics for songs
   is unrealistic - even moreso than filling in song names
   or scanning in covers by hand.
   If it's too much work, there's little point in having it.
** TODO Add tool support for lyrics
** TODO Add unit testing to tools
* TODO Convert format reference to HTML
  Not only would it be more accessible,
  but I could better integrate examples and give it basic interactivity.
  All of the diagrams and formulas must be kept as vector art
  so that they'll remain crisp when scaled.

  Unfortunately, Chrome doesn't do MathML and IE doesn't do SVG
  which makes it non-viable right now.
* TODO Add a dvdaplay utility
** TODO Unify trackplay/cdplay/dvdaplay widgets into audiotools.ui
** TODO Unify non-Urwid player into audiotools.ui
** TODO Add man page
* TODO Make internal functions static
  Encoders and decoders come with lots of helper functions.
  These should be defined statically whenever possible
  to avoid colliding with one another.
  As a side effect, these function names can now be shortened.
  (not applicable to FLAC and Ogg FLAC, which share a lot of functions)
** TODO decoders
   - [X] src/decoders/alac.c
   - [ ] src/decoders/aobpcm.c
   - [ ] src/decoders/mlp.c
   - [ ] src/decoders/ogg.c
   - [ ] src/decoders/shn.c
   - [ ] src/decoders/sine.c
   - [ ] src/decoders/vorbis.c
   - [X] src/decoders/wavpack.c
** TODO encoders
   - [X] src/encoders/alac.c
   - [ ] src/encoders/flac.c
   - [ ] src/encoders/shn.c
   - [X] src/encoders/wavpack.c
* TODO handle AIFF offset/block size in SSND chunk
* TODO Assimilate external codecs
  More of a long-term plan than anything else.
  It would be best to have as few external program
  dependencies as possible
** TODO Add internal Monkey's Audio codec
   This needs to be assimilated with a native decoder/encoder.
   I've seen these out in the wild, but only rarely.
*** TODO Add Python-based Monkey's Audio decoder
*** TODO Document Monkey's Audio decoding
*** TODO Add C-based Monkey's Audio decoder
*** TODO Add Python-based Monkey's Audio encoder
*** TODO Document Monkey's Audio encoding
*** TODO Add C-based Monkey's Audio encoder
*** TODO Add Monkey's Audio-specific unit tests
*** TODO Test encoder/decoder against reference
*** TODO Restore AudioFile-compatible Python interface
** DONE Add context manager support
   Having file-like objects work with "with" context managers
   should hopefully make file handle management a bit simpler.
*** DONE BitstreamReader
    Call .close() on internal stream,
    eat any exception (like the "file" object does) and return a False value
**** DONE Add documentation
**** DONE Add unit tests
*** DONE BitstreamWriters
    If no exception, call .flush() on internal stream and eat any exception.
    Then call .close() on internal stream and eat any exception also.
    Returns a False value.
**** DONE Add documentation
**** DONE Add unit tests
*** DONE PCMReaders
    Call .close() and eat any exception (like the "file" object does).
    - [X] aiff/AIFFReader
    - [X] au/AuReader
    - [X] audiotools/BufferedPCMReader
    - [X] audiotools/CounterPCMReader
    - [X] audiotools/LimitedPCMReader
    - [X] audiotools/PCMCat
    - [X] audiotools/PCMReader
    - [X] audiotools/PCMReaderDeHead
    - [X] audiotools/PCMReaderError
    - [X] audiotools/PCMReaderHead
    - [X] audiotools/PCMReaderProgress
    - [X] audiotools/ReorderedPCMReader
    - [X] decoders/ALACDecoder
    - [X] decoders/DVDA_Title
    - [X] decoders/FlacDecoder
    - [X] decoders/MP3Decoder
    - [X] decoders/OggFlacDecoder
    - [X] decoders/OpusDecoder
    - [X] decoders/SHNDecoder
    - [X] decoders/SameSample
    - [X] decoders/Sine_Mono
    - [X] decoders/Sine_Simple
    - [X] decoders/Sine_Stereo
    - [X] decoders/TTADecoder
    - [X] decoders/VorbisDecoder
    - [X] decoders/WavPackDecoder
    - [X] player/ThreadedPCMReader
    - [X] py_decoders/ALACDecoder
    - [X] py_decoders/FlacDecoder
    - [X] py_decoders/SHNDecoder
    - [X] py_decoders/TTADecoder
    - [X] py_decoders/WavPackDecoder
    - [X] test/EXACT_RANDOM_PCM_Reader
    - [X] test/Join_Reader
    - [X] test/MD5_Reader
    - [X] test/RANDOM_PCM_Reader
    - [X] test/Variable_Reader
    - [X] test_formats/CLOSE_PCM_Reader
    - [X] test_formats/ERROR_PCM_Reader
    - [X] test_streams/FrameListReader
    - [X] test_streams/MD5Reader
    - [X] test_streams/Raw
    - [X] test_streams/Simple_Sine
    - [X] test_streams/WastedBPS16
    - [X] wav/WaveReader
**** DONE Add documentation
**** DONE Add unit tests
* TODO Finish version 3.2
** TODO Add better metadata merge strategies
   These could be used by trackcat and others to combine MetaData objects
   in a useful way.
*** TODO Add metadata1.intersection(metadata2) -> metadata3 method
    metadata3 contains fields that are in both metadata1 and metadata2
    and contain the same values

    if both metadata objects are the same type, returns metadata in that type
    and optimize the merge to handle low-level metadata fields
    otherwise returns a generic MetaData object
**** DONE Update metadata implementations
     - [X] MetaData
     - [X] ApeTag
     - [X] FlacMetaData
     - [X] OggFlacMetaData
     - [X] ID3v22Comment
     - [X] ID3v23Comment
     - [X] ID3v24Comment
     - [X] ID3CommentPair
     - [X] ID3v1Comment
     - [X] M4A_META_Atom
     - [X] VorbisComment
**** DONE Add unit tests
     These should check that foreign fields are merged correctly,
     if applicable.
     - [X] MetaData
     - [X] ApeTag
     - [X] FlacMetaData
     - [X] ID3v22Comment
     - [X] ID3v23Comment
     - [X] ID3v24Comment
     - [X] ID3v1Comment
     - [X] M4A_META_Atom
     - [X] VorbisComment
**** DONE Update documentation
*** TODO Update trackcat to use .intersection() method
**** TODO Add unit tests
** TODO Handle pre-gaps that contain audio data
*** DONE Update cd2track
    If pre-gap is present, check it for data.

    If data is present, dump data to a "track 0" file
    with as much metadata as possible.

    If data is not present, skip it.
**** DONE Add unit tests
*** TODO Update track2cd
    If pre-gap is present, check for a "track 0" file of the same length.

    If track is present, prepend is to stream before writing to disc.

    If track is not present, prepend empty data to disc.
**** TODO Add unit tests
*** DONE Update tracksplit
    If pre-gap is present, check it for data.

    If data is present, dump data to a "track 0" file
    with as much metadata as possible.

    If data is not present, skip it.
**** DONE Add unit tests
*** DONE Update trackcat
    If pre-gap is present, check for a "track 0" file of the same length.

    If track is present, prepend is to stream before writing to image.

    If track is not present, prepend empty data to image.
**** DONE Add unit tests
** TODO Support MBID tags
   These are basically direct MusicBrainz links rather than using
   calculated disc IDs to perform lookups.
*** TODO Update MetaData formats to perform MBID lookups, if possible
    - [ ] ApeTag
    - [ ] FlacMetaData
    - [ ] ID3CommentPair
    - [ ] ID3v1Comment
    - [ ] ID3v22Comment
    - [ ] ID3v23Comment
    - [ ] ID3v24Comment
    - [ ] M4A
    - [ ] VorbisComment
**** TODO Add unit tests
*** TODO Update musicbrainz module to handle MBID lookup
    Given an ID, returns a list of MetaData objects
**** TODO Add unit tests
*** TODO Update utilities to support MBID lookup
    Basically anything that takes tracks and performs a lookup may benefit.
    - [ ] tracksplit
    - [ ] trackcat
    - [ ] track2track
    - [ ] tracktag
**** TODO Add unit tests
** TODO Add pre-emphasis support
*** TODO Add de-emphasis filter to pcmconverters
**** TODO Add unit test
*** TODO Add pre-emphasis filter to pcmconverters
**** TODO Add unit test
*** TODO Update cd2track
    If disc contains pre-emphasis, remove it during extraction
**** TODO Add unit test
*** TODO Update tracksplit
    If CD image contains pre-emphasis, remove it during extraction
**** TODO Add unit test
*** TODO Update track2cd
    If cuesheet is given and indicates pre-emphasis,
    re-add it when creating disc image for writing
**** TODO Add unit test
*** TODO Update trackcat
    If cuesheet is given an indicates pre-emphasis,
    re-add it when concatenating files to image
**** TODO Add unit test
** TODO Add CTDB support
   Need to figure out how the cuetools client interface actually works.
   Then attempt to use it wherever AccurateRip is used, if possible.
** TODO Improve test coverage
   - [X] set_metadata(None) to all formats
*** TODO Check repr on everything
    Verifying the contents isn't important;
    just make sure calling repr() doesn't throw an exception.
*** TODO audiotools/__init__
    - [X] output_text
    - [X] output_list
    - [X] output_table
    - [ ] threaded_transfer_framelist_data
    - [ ] pcm_cmp
    - [ ] pcm_frame_cmp
    - [ ] resampled_frame_count
    - [ ] read_sheet (IOError)
    - [X] iter_first
    - [X] iter_last
    - [X] most_numerous (len(item_list) == 0)
    - [X] most_numerous (all_differ)
    - [ ] cddareader_metadata_lookup
    - [ ] track_metadata_lookup
    - [ ] sheet_metadata_lookup
    - [ ] accuraterip_lookup (len(sorted_tracks) == 0)
**** TODO MetaData
     - [ ] __delattr__
     - [ ] empty_fields
     - [ ] __unicode__
     - [ ] delete_image
     - [ ] clean
**** TODO AudioFile
     - [ ] rich compare operators
**** DONE Sheet
     - [X] converted
     - [X] __eq__
     - [X] track_numbers
     - [X] track (KeyError)
     - [X] pre_gap
     - [X] track_offset
     - [X] track_length
**** DONE SheetTrack
     - [X] converted
     - [X] indexes
     - [X] index (KeyError)
     - [X] __eq__
**** DONE SheetIndex
     - [X] converted
     - [X] __eq__
**** TODO PCMReaderHead
     - [ ] check for negative pcm_frames
**** TODO PCMReaderDeHead
     - [ ] truncation longer than entire stream
     - [ ] read_closed
**** DONE ExecProgressQueue
*** DONE audiotools/accuraterip
    - [X] match_offset
*** TODO audiotools/aiff
**** TODO AiffAudio
     - [ ] verify
     - [ ] clean (with metadata)
*** TODO audiotools/ape
**** TODO ApeTag
     - [ ] get
     - [ ] index
**** DONE ApeTaggedAudio
     - [X] set_metadata(None)
     - [X] set_metadata with cuesheet
     - [X] delete_metadata with ReplayGain
     - [X] delete_metadata with cuesheet
*** TODO audiotools/cue
    - [ ] read_cuesheet_string (with ValueError)
**** DONE Track
     - [X] filename
     - [X] pre_emphasis
     - [X] copy_permitted
**** TODO audiotools/cue/tokrules
     - [ ] with illegal character
**** TODO audiotools/cue/yaccrules
     - [X] with flags
     - [ ] with syntax error
*** TODO audiotools/flac
**** DONE FlacMetaData
     - [X] converted(None)
     - [X] clean (multiple STREAMINFO)
     - [X] clean (multiple VORBIS_COMMENT)
     - [X] clean (multiple SEEKTABLE)
     - [X] clean (multiple CUESHEET)
     - [X] raw_info()
     check with all metadata blocks
**** DONE Flac_VORBISCOMMENT
     - [X] converted(Flac_VORBISCOMMENT)
     should return newly created object
**** TODO Flac_Cuesheet
     - [ ] track_length
     - [ ] get_metadata with catalog
     - [ ] converted with padding
**** TODO Flac_Cuesheet_track
     - [ ] filename
**** TODO Flac_PICTURE
     - [ ] setattr(type, ???)
     - [ ] type_string
**** TODO FlacAudio
     - [X] set_metadata(None)
     - [ ] set_metadata with channel mask in old comment
     - [X] get_cuesheet() -> None
     - [ ] from_pcm with UnsupportedChannelMask
     - [ ] has_foreign_wave_chunks() -> False
     - [ ] wave_header_footer() without chunks
     - [ ] has_foreign_aiff_chunks() -> False
     - [ ] aiff_header_footer() without chunks
     - [ ] convert() to WaveContainer
     - [ ] convert() to AiffContainer
     - [ ] delete_replay_gain() without VORBIS_COMMENT
     - [ ] clean on < 128 byte file
**** DONE FLAC_Data_Chunk
     is this still in use?
**** DONE FLAC_SSND_Chunk
     is this still in use?
**** DONE OggFlacMetaData
     - [X] converted(None)
**** TODO OggFlacAudio
     - [ ] from_pcm with UnsupportedChannelMask
*** TODO audiotools/freedb
    - [ ] xmcd_metadata without album artist
    - [ ] xmcd_metadata without track artist
*** TODO audiotools/id3
    - [ ] read_id3v2_comment (invalid header)
    - [ ] read_id3v2_comment (unsupported version)
    - [ ] skip_id3v2_comment (unsupported version)
    - [ ] skip_id3v2_comment (IOError)
    - [ ] total_id3v2_comments (unsupported version)
    - [ ] total_id3v2_comments (IOError)
**** TODO ID3v22Comment
     - [X] raw_info
     - [ ] parse
     - [ ] build
     - [ ] size
     - [ ] clean
**** TODO ID3v22_T__Frame
     - [ ] number() -> 0, no total
     - [ ] number() -> not numerical type
     - [ ] total() -> not numerical type
**** TODO ID3v22_TXX_Frame
**** TODO ID3v22_W__Frame
**** TODO ID3v22_WXX_Frame
**** TODO ID3v22_COM_Frame
     - [ ] clean
**** TODO ID3v22_PIC_Frame
     - [ ] invalid image from data
     - [ ] type_string
     - [ ] getattr -> AttributeError
     - [ ] setattr(type, ???)
     - [ ] setattr(desription, ???)
     - [ ] clean
**** TODO ID3v22Comment
     - [ ] copy
     - [ ] parse (invalid header)
     - [ ] parse (invalid major version)
     - [ ] parse (invalid minor version)
     - [ ] parse (TXX, WXX, W??)
     - [ ] delitem
     - [ ] items
     - [ ] getattr -> AttributeError
     - [ ] delattr(track/alum_number) unslashed
     - [ ] delattr unsupported field
     - [X] converted(None)
**** TODO ID3v23_TXXX_Frame
**** TODO ID3v23_WXXX_Frame
**** TODO ID3v23_APIC_Frame
     - [ ] __init__ invalid image
     - [ ] getattr -> AttributeError
     - [ ] setattr(pic_type)
     - [ ] setattr(description)
     - [ ] setattr(mime_type)
     - [ ] clean
**** TODO ID3v23Comment
     - [ ] parse invalid ID3 header
     - [ ] invalid major version
     - [ ] invalid minor version
     - [ ] parse (WXXX, W???)
**** TODO ID3v24_TXXX_Frame
     - [ ] __unicode__
     - [ ] parse
**** TODO ID3v24_APIC_Frame
     - [ ] setattr(type)
     - [ ] setattr(description)
     - [ ] setattr(mime_type)
     - [ ] clean
**** TODO ID3v24_WXXX_Frame
     - [ ] parse
**** TODO ID3v24_COMM_Frame
     - [ ] clean
**** TODO ID3v24Comment
     - [ ] parse (invalid ID3 header)
     - [ ] parse (invalid major version)
     - [ ] parse (invalid minor version)
     - [ ] parse (TXXX, WXXX, W???)
**** TODO ID3CommentPair
     - [ ] init id3v1 is not None
     - [X] converted(None)
     - [ ] convert(ID3v2Comment)
     - [X] raw_info
     - [ ] images (id3v2 is None)
     - [ ] clean
*** TODO audiotools/id3v1
**** TODO ID3v1Comment
     - [ ] getattr -> AttributeError
     - [ ] setattr(non field)
     - [ ] delattr(non field)
     - [X] converted(None)
     - [ ] images
***** TODO parse
      - [ ] track_name length != 30
      - [ ] artist_name length != 30
      - [ ] album_name length != 30
      - [ ] year length != 4
      - [ ] comment length != 28
      - [ ] track_number length != 1
      - [ ] genre length != 1
*** TODO audiotools/image
    - [ ] rename classes without __s
    - [ ] image_metrics -> InvalidImage
**** TODO __JPEG__
     - [ ] parse -> IOError
**** TODO __PNG__
     - [ ] parse -> InvalidPNG
     - [ ] parse -> IOError
     - [ ] parse -> grayscale
     - [ ] parse -> invalid PLTE
     - [ ] parse -> grayscale + alpha
**** TODO __BMP__
     - [ ] parse -> IOError
     - [ ] parse -> InvalidBMP
**** TODO __GIF__
     - [ ] parse -> IOError
     - [ ] parse -> InvalidGIF
**** TODO __TIFF__
     - [ ] parse big-endian TIFF
     - [ ] parse invalid magic number
*** TODO audiotools/m4a
    - [ ] get_m4a_atom -> KeyError
    - [ ] get_m4a_atom_offset -> KeyError
    - [ ] get_m4a_atom_offset -> IOError/KeyError
    - [ ] has_m4a_atom (if (length - 8) <= 0)
    - [ ] has_m4a_atom -> IOError
**** TODO M4ATaggedAduio
     - [ ] get_metadata -> KeyError if no meta
     - [ ] update_metadata (generate moov)
     - [ ] update_metadata (generate udta)
     - [ ] update_metadata (generate meta)
     - [X] set_metadata(None)
*** TODO audiotools/m4a_atoms
**** TODO M4A_Tree_Atom
     - [ ] has_child -> False
     - [ ] remove_child
     - [ ] child_offset -> KeyError
**** TODO M4A_META_Atom
     - [ ] getattr -> AttributeError
     - [ ] setattr -> replace_data_atom
     - [ ] delattr -> AttributeError
     - [X] converted(None)
*** TODO audiotools/mp3
**** TODO MP3Audio
     - [X] set_metadata(None)
     - [ ] update_metadata (id3v2 not None, id3v1 is None)
     - [ ] update_metadata (id3v2 None, id3v1 not None)
     - [ ] update_metadata (id3v2 None, id3v1 None)
     - [ ] clean (output_filename None, metadata None)
     - [ ] clean (output_filename not None, metadata None)
**** TODO MP2Audio
     - [ ] from_pcm (nonstandard sample rate)
*** TODO audiotools/opus
**** DONE OpusAudio
     - [X] init (invalid Ogg magic number)
     - [X] init (invalid Ogg version)
     - [X] init (invalid Opus type)
     - [X] init (invalid Opus version)
     - [X] init (invalid Opus channels)
     - [X] get_replay_gain
     - [X] set_replay_gain
     - [X] delete_replay_gain
*** TODO audiotools/shn
**** TODO ShortenAudio
     - [ ] __init__ -> IOError
     - [ ] has_foreign_wave_chunks -> True
     - [ ] has_foreign_wave_chunks -> odd-sized wave chunks
     - [ ] has_foreign_wave_chunks -> no foreign chunks found
     - [ ] has_foreign_aiff_chunks -> True
     - [ ] has_foreign_aiff_chunks -> odd-sized aiff chunks
     - [ ] has_foreign_aiff_chunks -> no foreign chunks found
     - [ ] from_pcm using external function
     - [ ] from_pcm (len(footer) == 0)
     - [ ] convert to wave container
     - [ ] convert to aiff container
*** TODO audiotools/toc
    - [ ] read_tocfile -> IOError
    - [ ] read_tocfile_string -> SheetException
    - [ ] write_tocfile
**** TODO TOCFile
     - [ ] get_metadata (catalog not None, cd_text not None)
**** TODO TOCTrack
     - [ ] get_metadata (isrc not None, cd_text not None)
     - [X] filename
     - [X] pre_emphasis
     - [X] copy_permitted (not False)
**** TODO audiotools/toc/tokrules
     - [ ] illegal character
**** TODO audiotools/toc/yaccrules
     - [ ] syntax error
*** TODO audiotools/tta
**** TODO TrueAudio
     - [ ] init -> invalid signature
     - [ ] init -> invalid format
     - [ ] from_pcm -> IOError to EncodingError
     - [ ] from_pcm -> frame count mismatch
     - [X] set_metadata(None)
     - [X] cuesheet in old metadata
     - [X] cuesheet in new metadata
     - [ ] get_cuesheet -> CueException
     - [X] set_cuesheet(None)
     - [ ] clean when metadata is None
*** TODO audiotools/vorbis
**** TODO VorbisAudio
     - [X] init -> invalid Ogg magic number
     - [X] init -> invalid Ogg version
     - [X] init -> invalid Vorbis type
     - [X] init -> invalid Vorbis header
     - [X] init -> invalid Vorbis version
     - [X] init -> invalid Vorbis framing bit
**** TODO VorbisChannelMask
     - [ ] channels
*** TODO audiotools/vorbiscomment
**** TODO VorbisComment
     - [ ] values
     - [ ] __contains__
     - [ ] getattr(non-MetaData attrib)
     - [ ] delattr(non-MetaData attrib)
     - [X] converted(None)
     - [ ] converted(FlacMetaData without VORBISCOMMENT)
     - [ ] clean(track_total/album_total with leading 0s)
*** TODO audiotools/wav
    - [X] wave_header -> total size too large
**** TODO RIFF_Chunk
     - [ ] handle odd sized chunks
**** TODO WaveReader
     - [ ] init -> file too small
     - [ ] init -> invalid RIFF
     - [ ] init -> invalid WAVE
     - [ ] init -> chunk header too small
     - [ ] init -> invalid chunk ID
     - [ ] init -> data chunk before fmt chunk
     - [ ] init -> no audio chunks
     - [ ] init -> no data chunk
     - [ ] read -> data chunk too small
**** TODO WaveAudio
     - [X] from_pcm -> total_pcm_frames too large
     - [X] from_pcm -> total_pcm_frames mismatch
     - [ ] track_name(format=None)
     - [ ] verify -> not enough data chunk
     - [ ] clean -> non audio chunk
*** TODO audiotools/wavpack
**** TODO WavPackAudio
     - [ ] blocks -> blocks_iter -> non wvpk block header
     - [ ] __read_info__ -> non wvpk block ID
     - [ ] __read_info__ -> nonstandard sample rate
     - [ ] __read_info__ -> no channel info sub block
     - [X] from_pcm -> total_pcm_frames mismatch
     - [ ] fmt_chunk
     - [ ] get_cuesheet -> CueException
     - [X] set_cuesheet(None)
*** TODO audiotools/py_decoders/flac
**** TODO FlacDecoder
     - [ ] init -> invalid FLAC file
     - [ ] read -> MD5 checksum mismatch
     - [ ] read -> invalid channel assignment
     - [ ] read -> CRC16 mismatch
     - [ ] read_frame_header -> invalid sync code
     - [ ] read_frame_header -> invalid sample rate
     - [ ] read_frame_header -> invalid bits-per-sample
     - [ ] read_frame_header -> CRC8 mismatch
     - [ ] read_subframe_header -> invalid subframe type
     - [ ] read_fixed_subframe -> invalid order
     - [ ] read_residual_partition(coding 0) -> escape code
     - [ ] read_residual_partition(coding 1) -> escape code
     - [ ] close
*** TODO audiotools/py_decoders/shn
**** TODO SHNDecoder
     - [ ] init -> unsupported file type
     - [ ] init -> non-audio RIFF chunks
     - [ ] init -> AIFF chunks
     - [ ] read_header -> invalid magic number
     - [ ] read_header -> unsupported version
     - [ ] read -> QLPC options
*** TODO audiotools/py_decoders/tta
**** TODO TTADecoder
     - [ ] init -> CRC mismatch in header
     - [ ] init -> CRC mismatch in seektable
     - [ ] init -> CRC mismatch in frame
     - [ ] close
*** TODO audiotools/py_decoders/wavpack
**** TODO WavPackDecoder
     - [ ] init -> nonstandard sample rate
     - [ ] read -> MD5 mismatch at end of stream
     - [ ] read -> IOError/ValueError
     - [ ] close
**** TODO Block_Header
     - [ ] invalid block ID
**** TODO Sub_Block
     - [ ] total_size large
**** TODO read_block
     - [ ] weights before terms
     - [ ] samples before terms
     - [ ] invalid samples byte count
     - [ ] bitstream before entropy vars
     - [ ] decorrelation weights not found
     - [ ] decorrelation samples not found
     - [ ] bitstream not found
     - [ ] CRC mismatch
**** TODO read_decorrelation_terms
     - [ ] invalid passes count
     - [ ] invalid term
**** TODO read_decorrelation_weights
     - [ ] invalid weight count
**** TODO read_decorrelation_samples
     - [ ] sub_block_size < 8, two channels
     - [ ] sub_block_size < 8, one channel
     - [ ] invalid term
*** TODO audiotools/py_encoders/alac
    - [ ] encode_frame -> residual overflow
    - [ ] tukey_window -> n > window2
    - [ ] encode_residuals -> residual overflow
*** TODO audiotools/py_encoders/flac
    - [ ] write_utf8 (value > 127)
    - [ ] tukey_window (n > window2)
    - [ ] compute_lpc_coefficients (estimate_best_order)
    - [ ] estimate_best_order
*** TODO audiotools/py_encoders/shn
**** TODO encode_shn
     - [X] all zeroes
     - [ ] write footer
*** TODO audiotools/py_encoders/wavpack
**** TODO encode_wavpack
     - [ ] write wave footer
**** TODO write_block
     - [ ] all samples are 0
** TODO Cleanup chunk-based interface (again)
   There needs to be a single-pass approach to encoding chunked files
   that doesn't involve pulling out the chunk data ahead of time.

   Perhaps a simple .to_wave(), .from_wave() approach
   which returns a whole .wav file as a file stream
   would be easiest.
** TODO Support building cue sheets from discs
   Find some way of grabbing the index points from a disc
   so cuesheets can be populated.
** TODO Cleanup AAC support
   Although the fdk-aac library doesn't have a compatible license,
   there must be *some* way to support AAC without the nasty
   hacks in place now.
** TODO Improve utility test coverage
*** TODO Add some basic tests to ensure they still works
    - [ ] cdinfo
    - [ ] cdplay
    - [ ] trackplay
    - [ ] trackverify
