diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,340 @@
+		    GNU GENERAL PUBLIC LICENSE
+		       Version 2, June 1991
+
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+     59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+			    Preamble
+
+  The licenses for most software are designed to take away your
+freedom to share and change it.  By contrast, the GNU General Public
+License is intended to guarantee your freedom to share and change free
+software--to make sure the software is free for all its users.  This
+General Public License applies to most of the Free Software
+Foundation's software and to any other program whose authors commit to
+using it.  (Some other Free Software Foundation software is covered by
+the GNU Library General Public License instead.)  You can apply it to
+your programs, too.
+
+  When we speak of free software, we are referring to freedom, not
+price.  Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+this service if you wish), that you receive source code or can get it
+if you want it, that you can change the software or use pieces of it
+in new free programs; and that you know you can do these things.
+
+  To protect your rights, we need to make restrictions that forbid
+anyone to deny you these rights or to ask you to surrender the rights.
+These restrictions translate to certain responsibilities for you if you
+distribute copies of the software, or if you modify it.
+
+  For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must give the recipients all the rights that
+you have.  You must make sure that they, too, receive or can get the
+source code.  And you must show them these terms so they know their
+rights.
+
+  We protect your rights with two steps: (1) copyright the software, and
+(2) offer you this license which gives you legal permission to copy,
+distribute and/or modify the software.
+
+  Also, for each author's protection and ours, we want to make certain
+that everyone understands that there is no warranty for this free
+software.  If the software is modified by someone else and passed on, we
+want its recipients to know that what they have is not the original, so
+that any problems introduced by others will not reflect on the original
+authors' reputations.
+
+  Finally, any free program is threatened constantly by software
+patents.  We wish to avoid the danger that redistributors of a free
+program will individually obtain patent licenses, in effect making the
+program proprietary.  To prevent this, we have made it clear that any
+patent must be licensed for everyone's free use or not licensed at all.
+
+  The precise terms and conditions for copying, distribution and
+modification follow.
+
+		    GNU GENERAL PUBLIC LICENSE
+   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+  0. This License applies to any program or other work which contains
+a notice placed by the copyright holder saying it may be distributed
+under the terms of this General Public License.  The "Program", below,
+refers to any such program or work, and a "work based on the Program"
+means either the Program or any derivative work under copyright law:
+that is to say, a work containing the Program or a portion of it,
+either verbatim or with modifications and/or translated into another
+language.  (Hereinafter, translation is included without limitation in
+the term "modification".)  Each licensee is addressed as "you".
+
+Activities other than copying, distribution and modification are not
+covered by this License; they are outside its scope.  The act of
+running the Program is not restricted, and the output from the Program
+is covered only if its contents constitute a work based on the
+Program (independent of having been made by running the Program).
+Whether that is true depends on what the Program does.
+
+  1. You may copy and distribute verbatim copies of the Program's
+source code as you receive it, in any medium, provided that you
+conspicuously and appropriately publish on each copy an appropriate
+copyright notice and disclaimer of warranty; keep intact all the
+notices that refer to this License and to the absence of any warranty;
+and give any other recipients of the Program a copy of this License
+along with the Program.
+
+You may charge a fee for the physical act of transferring a copy, and
+you may at your option offer warranty protection in exchange for a fee.
+
+  2. You may modify your copy or copies of the Program or any portion
+of it, thus forming a work based on the Program, and copy and
+distribute such modifications or work under the terms of Section 1
+above, provided that you also meet all of these conditions:
+
+    a) You must cause the modified files to carry prominent notices
+    stating that you changed the files and the date of any change.
+
+    b) You must cause any work that you distribute or publish, that in
+    whole or in part contains or is derived from the Program or any
+    part thereof, to be licensed as a whole at no charge to all third
+    parties under the terms of this License.
+
+    c) If the modified program normally reads commands interactively
+    when run, you must cause it, when started running for such
+    interactive use in the most ordinary way, to print or display an
+    announcement including an appropriate copyright notice and a
+    notice that there is no warranty (or else, saying that you provide
+    a warranty) and that users may redistribute the program under
+    these conditions, and telling the user how to view a copy of this
+    License.  (Exception: if the Program itself is interactive but
+    does not normally print such an announcement, your work based on
+    the Program is not required to print an announcement.)
+
+These requirements apply to the modified work as a whole.  If
+identifiable sections of that work are not derived from the Program,
+and can be reasonably considered independent and separate works in
+themselves, then this License, and its terms, do not apply to those
+sections when you distribute them as separate works.  But when you
+distribute the same sections as part of a whole which is a work based
+on the Program, the distribution of the whole must be on the terms of
+this License, whose permissions for other licensees extend to the
+entire whole, and thus to each and every part regardless of who wrote it.
+
+Thus, it is not the intent of this section to claim rights or contest
+your rights to work written entirely by you; rather, the intent is to
+exercise the right to control the distribution of derivative or
+collective works based on the Program.
+
+In addition, mere aggregation of another work not based on the Program
+with the Program (or with a work based on the Program) on a volume of
+a storage or distribution medium does not bring the other work under
+the scope of this License.
+
+  3. You may copy and distribute the Program (or a work based on it,
+under Section 2) in object code or executable form under the terms of
+Sections 1 and 2 above provided that you also do one of the following:
+
+    a) Accompany it with the complete corresponding machine-readable
+    source code, which must be distributed under the terms of Sections
+    1 and 2 above on a medium customarily used for software interchange; or,
+
+    b) Accompany it with a written offer, valid for at least three
+    years, to give any third party, for a charge no more than your
+    cost of physically performing source distribution, a complete
+    machine-readable copy of the corresponding source code, to be
+    distributed under the terms of Sections 1 and 2 above on a medium
+    customarily used for software interchange; or,
+
+    c) Accompany it with the information you received as to the offer
+    to distribute corresponding source code.  (This alternative is
+    allowed only for noncommercial distribution and only if you
+    received the program in object code or executable form with such
+    an offer, in accord with Subsection b above.)
+
+The source code for a work means the preferred form of the work for
+making modifications to it.  For an executable work, complete source
+code means all the source code for all modules it contains, plus any
+associated interface definition files, plus the scripts used to
+control compilation and installation of the executable.  However, as a
+special exception, the source code distributed need not include
+anything that is normally distributed (in either source or binary
+form) with the major components (compiler, kernel, and so on) of the
+operating system on which the executable runs, unless that component
+itself accompanies the executable.
+
+If distribution of executable or object code is made by offering
+access to copy from a designated place, then offering equivalent
+access to copy the source code from the same place counts as
+distribution of the source code, even though third parties are not
+compelled to copy the source along with the object code.
+
+  4. You may not copy, modify, sublicense, or distribute the Program
+except as expressly provided under this License.  Any attempt
+otherwise to copy, modify, sublicense or distribute the Program is
+void, and will automatically terminate your rights under this License.
+However, parties who have received copies, or rights, from you under
+this License will not have their licenses terminated so long as such
+parties remain in full compliance.
+
+  5. You are not required to accept this License, since you have not
+signed it.  However, nothing else grants you permission to modify or
+distribute the Program or its derivative works.  These actions are
+prohibited by law if you do not accept this License.  Therefore, by
+modifying or distributing the Program (or any work based on the
+Program), you indicate your acceptance of this License to do so, and
+all its terms and conditions for copying, distributing or modifying
+the Program or works based on it.
+
+  6. Each time you redistribute the Program (or any work based on the
+Program), the recipient automatically receives a license from the
+original licensor to copy, distribute or modify the Program subject to
+these terms and conditions.  You may not impose any further
+restrictions on the recipients' exercise of the rights granted herein.
+You are not responsible for enforcing compliance by third parties to
+this License.
+
+  7. If, as a consequence of a court judgment or allegation of patent
+infringement or for any other reason (not limited to patent issues),
+conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License.  If you cannot
+distribute so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you
+may not distribute the Program at all.  For example, if a patent
+license would not permit royalty-free redistribution of the Program by
+all those who receive copies directly or indirectly through you, then
+the only way you could satisfy both it and this License would be to
+refrain entirely from distribution of the Program.
+
+If any portion of this section is held invalid or unenforceable under
+any particular circumstance, the balance of the section is intended to
+apply and the section as a whole is intended to apply in other
+circumstances.
+
+It is not the purpose of this section to induce you to infringe any
+patents or other property right claims or to contest validity of any
+such claims; this section has the sole purpose of protecting the
+integrity of the free software distribution system, which is
+implemented by public license practices.  Many people have made
+generous contributions to the wide range of software distributed
+through that system in reliance on consistent application of that
+system; it is up to the author/donor to decide if he or she is willing
+to distribute software through any other system and a licensee cannot
+impose that choice.
+
+This section is intended to make thoroughly clear what is believed to
+be a consequence of the rest of this License.
+
+  8. If the distribution and/or use of the Program is restricted in
+certain countries either by patents or by copyrighted interfaces, the
+original copyright holder who places the Program under this License
+may add an explicit geographical distribution limitation excluding
+those countries, so that distribution is permitted only in or among
+countries not thus excluded.  In such case, this License incorporates
+the limitation as if written in the body of this License.
+
+  9. The Free Software Foundation may publish revised and/or new versions
+of the General Public License from time to time.  Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+Each version is given a distinguishing version number.  If the Program
+specifies a version number of this License which applies to it and "any
+later version", you have the option of following the terms and conditions
+either of that version or of any later version published by the Free
+Software Foundation.  If the Program does not specify a version number of
+this License, you may choose any version ever published by the Free Software
+Foundation.
+
+  10. If you wish to incorporate parts of the Program into other free
+programs whose distribution conditions are different, write to the author
+to ask for permission.  For software which is copyrighted by the Free
+Software Foundation, write to the Free Software Foundation; we sometimes
+make exceptions for this.  Our decision will be guided by the two goals
+of preserving the free status of all derivatives of our free software and
+of promoting the sharing and reuse of software generally.
+
+			    NO WARRANTY
+
+  11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
+FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW.  EXCEPT WHEN
+OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
+PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
+OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.  THE ENTIRE RISK AS
+TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU.  SHOULD THE
+PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
+REPAIR OR CORRECTION.
+
+  12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
+REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
+INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
+OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
+TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
+YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
+PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
+POSSIBILITY OF SUCH DAMAGES.
+
+		     END OF TERMS AND CONDITIONS
+
+	    How to Apply These Terms to Your New Programs
+
+  If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+  To do so, attach the following notices to the program.  It is safest
+to attach them to the start of each source file to most effectively
+convey the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+    <one line to give the program's name and a brief idea of what it does.>
+    Copyright (C) <year>  <name of author>
+
+    This program is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    This program is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with this program; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+
+Also add information on how to contact you by electronic and paper mail.
+
+If the program is interactive, make it output a short notice like this
+when it starts in an interactive mode:
+
+    Gnomovision version 69, Copyright (C) year  name of author
+    Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+    This is free software, and you are welcome to redistribute it
+    under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License.  Of course, the commands you use may
+be called something other than `show w' and `show c'; they could even be
+mouse-clicks or menu items--whatever suits your program.
+
+You should also get your employer (if you work as a programmer) or your
+school, if any, to sign a "copyright disclaimer" for the program, if
+necessary.  Here is a sample; alter the names:
+
+  Yoyodyne, Inc., hereby disclaims all copyright interest in the program
+  `Gnomovision' (which makes passes at compilers) written by James Hacker.
+
+  <signature of Ty Coon>, 1 April 1989
+  Ty Coon, President of Vice
+
+This General Public License does not permit incorporating your program into
+proprietary programs.  If your program is a subroutine library, you may
+consider it more useful to permit linking proprietary applications with the
+library.  If this is what you want to do, use the GNU Library General
+Public License instead of this License.
diff --git a/RBR/FreqTable.lhs b/RBR/FreqTable.lhs
new file mode 100644
--- /dev/null
+++ b/RBR/FreqTable.lhs
@@ -0,0 +1,94 @@
+\section{FreqTable}
+
+Building a Frequency Table for keys.
+
+Constructs the table of all possible keys, so be careful.
+
+\begin{code}
+
+module RBR.FreqTable (FreqTable(..)
+                 ,freqtable_int,freqtable_integer    -- constructors
+                 ,sparsetable_int, sparsetable_integer
+                 ) where
+
+import Bio.Sequence
+
+import Data.IntMap as IM
+import Data.Map as M
+import Data.List as L
+
+data FreqTable a = FT { count :: a -> Int
+                      , counts :: [(a,Int)]
+                      }
+
+\end{code}
+
+\subsection{Constructing the frequency table}
+
+Frequency table, counting occurences.
+
+\begin{code}
+
+freqtable_int :: HashF Int -> [Sequence] -> FreqTable Int
+freqtable_int kf ss = FT (\k -> IM.findWithDefault 0 k idx) (IM.toList idx)
+    where
+    idx = foldl' myupdate IM.empty $ concatMap (L.map fst . hashes kf . seqdata) ss
+    myupdate m k = let x = 1 + IM.findWithDefault 0 k m
+                   in x `seq` IM.insert k x m
+
+freqtable_integer :: HashF Integer -> [Sequence] -> FreqTable Integer
+freqtable_integer kf ss = FT (\k -> M.findWithDefault 0 k idx) (M.toList idx)
+    where
+    idx = foldl' myupdate M.empty $ concatMap (L.map fst . hashes kf . seqdata) ss
+    myupdate m k = let x = 1 + M.findWithDefault 0 k m
+                   in x `seq` M.insert k x m
+
+
+-- alternative: use
+--   accumArray (+) 0 (minBound, maxBound) . (map (\x->(x,1)))
+
+\end{code}
+
+\subsection{Sparse maps}
+
+For parameter k, increment all existing keys, insert new keys only
+when a gap larger than k would otherwise result.   Keys are added
+weight according to distance to next inserted key, so that the sum
+weight of a sequence in the map is independent of k.
+
+For this to be useful, masking must be performed against a sliding
+average.  (How will this work against libraries?)
+
+Invariant: sparsetable_int 1 == freqtable_int
+
+\begin{code}
+
+sparsetable_int :: Int -> HashF Int -> [Sequence] -> FreqTable Int
+sparsetable_int step kf ss = FT (\k -> find k idx) (IM.toList idx)
+    where
+    find = IM.findWithDefault 0
+    idx = foldl' ins1 IM.empty ss
+    ins1 ix s = let ks = L.map fst $ hashes kf $ seqdata s
+                in go ix (step-1) ks
+    -- insert keys that already exist, or when the skip-counter is zero
+    go ix' n (k:ks) | n==0 || find k ix' /= 0
+                      || L.null ks = let x = find k ix'+step-n
+                                     in x `seq` go (IM.insert k x ix') (step-1) ks
+                    | True           = go ix' (n-1) ks
+    go ix' _n [] = ix'
+
+sparsetable_integer :: Int -> HashF Integer -> [Sequence] -> FreqTable Integer
+sparsetable_integer step kf ss = FT (\k -> find k idx) (M.toList idx)
+    where
+    find = M.findWithDefault 0
+    idx = foldl' ins1 M.empty ss
+    ins1 ix s = let ks = L.map fst $ hashes kf $ seqdata s
+                in go ix (step-1) ks
+    -- insert keys that already exist, or when the skip-counter is zero
+    go ix' n (k:ks) | n==0 || find k ix' /= 0
+                      || L.null ks = let x = find k ix'+step-n
+                                     in x `seq` go (M.insert k x ix') (step-1) ks
+                    | True           = go ix' (n-1) ks
+    go ix' _n [] = ix'
+
+\end{code}
diff --git a/RBR/MCT.lhs b/RBR/MCT.lhs
new file mode 100644
--- /dev/null
+++ b/RBR/MCT.lhs
@@ -0,0 +1,105 @@
+\section{MaskCount}
+
+Examine each nucleotide from two data sets, and count the Ns (or Xs?).
+Separate into A AB B 0.  Also (optionally?) count masked region sizes
+to generate gnuplot'able histograms.
+
+TODO:
+   - parameter to specify lower case/N/X as masked
+   - justify columns in output
+   - output percentages
+
+\begin{code}
+{-# LANGUAGE BangPatterns, FlexibleContexts #-}
+module Main where
+
+import Bio.Sequence
+
+import RBR.TextFormat
+
+import Control.Monad (when)
+import Data.Char (isLower)
+import Data.List (foldl',intersperse,partition)
+import System.Environment (getArgs)
+import System.Exit (exitWith,ExitCode(..))
+import Text.Printf (printf, PrintfType)
+
+main :: IO ()
+main = do
+       (opts,[a,b]) <- parseArgs
+       as <- readFasta a
+       bs <- readFasta b
+       let res = zipComp as bs
+       when (elem 'l' opts)
+         (do
+          putStrLn ("# fraction of masked positions: "++a++" both "++b++"none")
+          putStrLn $ unlines $ map printres res)
+       when (elem 's' opts)
+         (do
+          putStrLn "# Summary:"
+          putStrLn $ printSum a b $ foldl' add (CS 0 0 0 0) (map snd res))
+
+parseArgs :: IO (String, [String])
+parseArgs = do
+       as <- getArgs
+       let (opts,files) = partition ((=='-').head) as
+       when (length files /= 2) usage
+       let short = concat $ map tail opts
+       return (short,files)
+
+usage :: IO a
+usage = do putStrLn "mc: compare two FASTA files for masked nucleotides"
+           putStrLn "usage: mc [-s|-l] file_1 file_2"
+           exitWith $ ExitFailure 1
+
+data CompStruct = CS !Int !Int !Int !Int
+
+add :: CompStruct -> CompStruct -> CompStruct
+add (CS a ab b o) (CS a' ab' b' o') = CS (a+a') (ab+ab') (b+b') (o+o')
+
+-- construct the percentage similar nucleotides
+printres :: (String, CompStruct) -> String
+printres (s,CS a ab b o) = let tot = a+ab+b+o
+    in concat $ intersperse " "
+       $ s : map (percent tot) [a,ab,b,o]
+
+printSum :: String -> String -> CompStruct -> String
+printSum a b (CS ca cab cb co) = unlines (["Summary for "++a++" vs. "++b++":\n"]
+   ++ columns " " [ map ("  "++) ["unmasked in both:","masked in both:"
+                                 ,"masked in "++a++":","masked in "++b++":"]
+                  , map show [co, cab, ca, cb]
+                  , map (paren . percent tot) [co,cab,ca,cb ]])
+   ++ "Pearson's phi: "++printf "%.3f" (phi :: Double)
+  where tot = co+cab+ca+cb
+        phi = let [a,b,ab,o] = map ((/(fromIntegral tot)) . fromIntegral) [ca,cb,cab,co]
+              in (ab*o-a*b)/sqrt((ab+a)*(ab+b)*(a+o)*(b+o))
+
+paren :: String -> String
+paren s = "("++s++"%)"
+
+percent :: (PrintfType (Double -> t), Integral a, Integral a1) => a1 -> a -> t
+percent tot x = printf "%.2f" (100*fromIntegral x/fromIntegral tot :: Double)
+
+zipComp :: [Sequence] -> [Sequence] -> [(String,CompStruct)]
+zipComp as bs = map (z1 (CS 0 0 0 0)) $ zipSeqs as bs
+
+z1 :: CompStruct -> (Sequence, Sequence) -> (String, CompStruct)
+z1 cs (a,b) = (toStr $ seqlabel a, foldl' collect cs $ zip (get a) (get b))
+    where get s = map isLower $ map (s!) [0..seqlength s-1]
+                 -- s/isLower/isN/ for N detect.
+
+zipSeqs :: [Sequence] -> [Sequence] -> [(Sequence,Sequence)]
+zipSeqs [] [] = []
+zipSeqs (a:as) (b:bs) =
+    if seqlabel a == seqlabel b then (a,b):zipSeqs as bs
+    else error ("sequence sets differ (offending sequences: '"
+             ++ toStr (seqlabel a) ++ "' and '" ++ toStr (seqlabel b) ++"'.")
+zipSeqs _ _ = error "extraneous sequences"
+
+collect :: CompStruct -> (Bool, Bool) -> CompStruct
+collect (CS a ab b o) (True,True)   = (CS a (ab+1) b o)
+collect (CS a ab b o) (True,False)  = (CS (a+1) ab b o)
+collect (CS a ab b o) (False,True)  = (CS a ab (b+1) o)
+collect (CS a ab b o) (False,False) = (CS a ab b (o+1))
+
+\end{code}
diff --git a/RBR/Options.lhs b/RBR/Options.lhs
new file mode 100644
--- /dev/null
+++ b/RBR/Options.lhs
@@ -0,0 +1,74 @@
+\subsection{Option handling}
+
+You know the drill...
+
+\begin{code}
+
+module RBR.Options (version,usage,getOptions,Opts(..),parseargs,MaskWith(..)) where
+
+import System.Environment (getArgs)
+import System.Console.GetOpt
+import System.Exit
+
+version, usagemsg :: String
+version = "0.8.4"
+usagemsg = "Usage: rbr [-k <k>] [options] FILE.."
+
+getOptions :: IO ([Opts -> IO Opts], [String], [String])
+getOptions  = getArgs >>= (return . getOpt Permute options)
+
+usage :: [String] -> String
+usage errs = usageInfo (concat errs ++ usagemsg) options
+
+data MaskWith = Lower | Ns | Xs
+
+options :: [OptDescr (Opts -> IO Opts)]
+options = [Option ['k'] ["word-size"] (ReqArg (\arg opt -> return opt { kval = read arg }) "Int") "Word size"
+          ,Option [] ["sparse-keys"]  (ReqArg (\arg opt -> return opt { skip = read arg })    "Int") "Skip length"
+--          ,Option [] ["shape"] (ReqArg ...) "Key shape"
+          ,Option ['N'] ["no-stats"]   (NoArg  (\opt -> return opt { stats = False }))         "Omit statistical masking"
+          ,Option ['g'] ["gapclosing"] (ReqArg (\arg opt -> return opt { gap = read arg })    "Int") "Close gaps"
+          ,Option ['s'] ["stringency"] (ReqArg (\arg opt -> return opt { string = read arg }) "Float") "Mask stringecy"
+          ,Option ['t'] ["threshold"]  (ReqArg (\arg opt -> return opt { thresh = read arg }) "Float") "Mask threshold"
+          ,Option [] ["preserve-lower-case"] (NoArg (\opt -> return opt { preserve_lower = True })) "Preserve lower case in input sequences"
+
+          ,Option ['D'] ["distribution"] (NoArg (\opt -> return opt { distr = True }))            "Distribution output"
+          ,Option ['l'] ["library"]    (ReqArg (\arg opt -> return opt {lib = Just arg}) "file") "Mask words from library"
+          ,Option ['L'] ["lower-case"] (NoArg (\opt -> return opt { lower = Lower }))    "Mask using lower case letters"
+          ,Option ['n'] ["n-mask"] (NoArg (\opt -> return opt { lower = Ns }))    "Mask using 'n's"
+          ,Option ['x'] ["x-mask"] (NoArg (\opt -> return opt { lower = Xs }))    "Mask using 'X's"
+          ,Option [] ["server"]        (NoArg (\opt -> return opt { server = True }))     "Run in server mode"
+          ,Option ['o'] ["output-file"] (ReqArg (\arg opt -> return opt { ofile = Just arg }) "file") "Output file"
+	  ,Option ['v'] ["verbose"]    (NoArg (\opt -> return opt { verb = True })) "Display progress"
+
+          ,Option ['h'] ["help"]    (NoArg (\_ -> do {putStrLn $ usage []; exitWith ExitSuccess })) "Display help"
+          ,Option ['V'] ["version"] (NoArg (\_ -> do {putStrLn $ "rbr version "++version; exitWith ExitSuccess })) "Display version"
+          ]
+
+data Opts = Opts { kval :: Int, ofile :: Maybe FilePath
+                 , stats, preserve_lower :: Bool
+                 , thresh, string  :: Double
+                 , gap :: Int
+                 , lower :: MaskWith
+                 , distr, verb, server :: Bool
+                 , lib :: Maybe FilePath
+                 , skip :: Int
+                 }
+
+defaultopts :: Opts
+defaultopts = Opts 16 Nothing True False 0 0 0 Lower False False False Nothing 0
+
+parseargs :: [Opts -> IO Opts] -> IO Opts
+parseargs args = foldl (>>=) (return defaultopts) args >>= fixS >>= fixT
+    where fixS opts = return $ case (string opts,lower opts) of -- sensible(?) defaults
+                         (0,Ns) -> opts { string = 2.0 }
+                         (0,Xs) -> opts { string = 2.0 }
+                         (0,Lower) -> opts { string = 1.1 }
+                         _ -> opts
+          fixT opts = return $ case (thresh opts,lower opts) of -- sensible(?) defaults
+                         (0,Ns) -> opts { thresh = 8.0 }
+                         (0,Xs) -> opts { thresh = 8.0 }
+                         (0,Lower) -> opts { thresh = 5.0 }
+                         _ -> opts
+
+\end{code}
diff --git a/RBR/RBR.lhs b/RBR/RBR.lhs
new file mode 100644
--- /dev/null
+++ b/RBR/RBR.lhs
@@ -0,0 +1,385 @@
+\section{RBR - RepeatBeater revisited}
+
+RBR works by examining word frequencies in EST data, and
+based on the assumption that expression of positions within each gene
+is relatively uniform, identifies extremely common words as potential
+repeats.
+
+A predecessor to RBR was implemented as some scripts parsing
+\texttt{xsact} output, this is a revised, standalone implmentation.
+
+\begin{code}
+{-# LANGUAGE CPP #-}
+
+module Main (main) where
+
+import Bio.Sequence
+
+import RBR.FreqTable
+import RBR.Stats    hiding (median,uv_add)
+import RBR.Util     (interactl)
+import RBR.Options
+
+-- import Text.Printf
+import qualified Data.ByteString.Lazy.Char8 as B (map)
+
+import System.IO
+import System.IO.Unsafe
+import Control.Monad
+import Data.List as List
+import Data.Char as Char
+
+import Data.Map hiding (null,filter,map,elems,keys)
+
+#define perror (\s->error ("Program error in function \""++s++"\" at "++__FILE__++":"++show (__LINE__::Int)++\
+"\nPlease report this incident to <ketil@ii.uib.no>."))
+
+main :: IO ()
+main = do
+       (opt1,non,err) <- getOptions
+       -- print (opt1,non,err)
+       when ((null non && null opt1) || not (null err)) (error $ usage err)
+       opts <- parseargs opt1
+       case non of [] -> do ss <- hReadFasta stdin
+                            main_real opts ("<stdin>", return ss)
+                   [f1] -> main_real opts (f1, readFasta f1)
+                   fs   -> error ("Only supply one input file (you supplied: "++show fs++")")
+
+header :: Opts -> [FilePath] -> String
+header opts _inputs = "# rbr version "++version++", mask="++(case (lower opts) of Lower -> "L"; Ns -> "n"; Xs -> "X")++
+                      " k="++show (kval opts)++" stringency="++show (string opts)++
+                      " thresh="++show (thresh opts)++" g="++show (gap opts) ++ " skip="++ show (skip opts)
+
+\end{code}
+
+\subsection{The real main}
+
+\begin{code}
+
+debug :: Bool -> String -> IO ()
+debug p m = when p $ hPutStr stderr m
+
+countIO :: String -> String -> Int -> [a] -> IO [a]
+countIO msg post step xs = sequence $ map unsafeInterleaveIO ((blank >> outmsg (0::Int) >> c):cs)
+   where (c:cs) = ct 0 xs
+         output   = hPutStr stderr
+         blank    = output ('\r':take 70 (repeat ' '))
+         outmsg x = output ('\r':msg++show x) >> hFlush stderr
+         ct s ys = let (a,b) = splitAt (step-1) ys
+                       next  = s+step
+                   in case b of [b1] -> map return a ++ [outmsg (s+step) >> hPutStr stderr post >> return b1]
+                                []   -> map return (init a) ++ [outmsg (s+length a) >> hPutStr stderr post >> return (last a)]
+                                _ -> map return a ++ [outmsg s >> return (head b)] ++ ct next (tail b)
+
+main_real :: Opts -> (String, IO [Sequence]) -> IO ()
+main_real opts (ifile,ssio) = do
+   when (verb opts || distr opts) $ putStrLn $ header opts [ifile]
+   when (verb opts) $ do
+      debug (verb opts) ("Reading file: "++ifile++"\n")
+      ss <- ssio
+      debug (verb opts) ("Number of sequences: "++show (length ss)++"\n")
+   if kval opts <= 16 then main_cont make_ft_int opts ssio
+                      else main_cont make_ft_integer opts ssio
+
+main_cont :: Integral a => MkFT a -> Opts -> IO [Sequence] -> IO ()
+main_cont make_ft opts ssio = do
+   mask1 <- mask_func make_ft opts ssio
+   ss'' <- if (verb opts) then countIO "  ...masking sequences: " ", done.\n" 100 =<< ssio
+                          else ssio
+   if (server opts) then do
+                ft <- make_ft "Building index array" (verb opts) (skip opts) (kval opts) ssio
+                interactl "READY: " (concatMap (main_server opts ft ss'') . words)
+      else do if (distr opts) then main_coverage make_ft opts ssio
+                              else main_masked opts mask1 ss''
+
+type MkFT a = String -> Bool -> Int -> Int -> IO [Sequence] -> IO (FreqTable a)
+
+make_ft_int :: MkFT Int
+make_ft_int msg vb skp k ssio = do
+  ss' <- if vb then countIO  ("  "++msg++": ") ", done.\n" 100 =<< ssio
+         else ssio
+  if skp > 0 then return (sparsetable_int skp (rcontig k) ss')
+             else return (freqtable_int (rcontig k) ss')
+
+make_ft_integer :: MkFT Integer
+make_ft_integer msg vb skp k ssio = do
+  ss' <- if vb then countIO  ("  "++msg++": ") ", done.\n" 100 =<< ssio
+         else ssio
+  if skp>0 then return (sparsetable_integer skp (rcontig k) ss')
+            else return (freqtable_integer (rcontig k) ss')
+
+main_coverage :: Integral a => MkFT a -> Opts -> IO [Sequence] -> IO ()
+main_coverage make_ft opts ssio = do
+    ft <- make_ft "Building index array" (verb opts) (skip opts) (kval opts) ssio
+    let k = kval opts
+        sk = skip opts
+        (stdevs,b2,strg) = (thresh opts, 5, string opts)
+	seq_cov s = let cv = coverage k sk (rcontig k) ft s
+                        ls = sortv $ blunt b2
+	                     $ map (\l->(fromIntegral $ head l,length l)) $ group $ sort cv
+                        (mu,stdv) = distrib strg ls
+                        t  = mu + max b2 (stdevs*stdv)
+                    in (seqheader s, if null cv || null ls then (0,0,0) else (mu,stdv,t),cv) -- todo: round!
+        write = case ofile opts of Nothing -> putStrLn; Just f -> writeFile f
+    debug (verb opts) "Calculating coverage\n"
+    ss <- ssio
+    write $ unlines $ map show $ map seq_cov ss
+
+main_masked :: Opts -> (Sequence->Sequence) -> [Sequence] -> IO ()
+main_masked opts mf ss = do
+    let ms = if kval opts == 0 then ss else map mf ss
+        write = case ofile opts of Nothing -> hWriteFasta stdout
+                                   Just f -> writeFasta f
+    write ms
+
+mask_func :: Integral a => MkFT a -> Opts -> IO [Sequence] -> IO (Sequence -> Sequence)
+mask_func make_ft opts ssio =
+                    do let k = kval opts
+                           g = gap  opts
+                           sk = skip opts
+                           gc = if g > 0 then closegaps (g+k-1) else id
+                           sparm = (thresh opts, 5, string opts)
+                           upcase = if preserve_lower opts then id else upcaseSequence
+                       ms <- do case stats opts of
+                                  False -> return []
+                                  True  -> do ft <- make_ft "Indexing input" (verb opts) sk k ssio
+                                              return [mask_stat k sk sparm ft]
+                       mt <- case lib opts of
+                               Nothing -> return []
+                               Just f -> do lft <- make_ft "Indexing repeat library" (verb opts) sk k $ readFasta f
+                                            return [mask_table k sk lft]
+                       return ((case ms++mt of [] -> id
+                                               xs -> mask_generic (lower opts) (k+if sk==0 then 0 else sk-1)
+                                                     (gc . min_mask ((sk+2) `div` 2) . combine xs)) . upcase)
+
+upcaseSequence :: Sequence -> Sequence
+upcaseSequence (Seq l d q) = (Seq l (B.map toUpper d) q)
+
+-- sparse keys can give aberrant counts, so require more than one consequtive
+-- todo: really really convert to RLL-encoded whitelist
+min_mask :: Int -> [Bool] -> [Bool]
+min_mask _ [] = []
+min_mask sk xs@(True:_) = let (t,f) = span id xs in t ++ min_mask sk f
+min_mask sk xs@(False:_) = let (f,t) = span not xs in (if length f >= sk then f
+                                                       else take (length f) (repeat True)) ++ min_mask sk t
+
+combine :: [a -> [Bool]] -> (a -> [Bool])
+combine []  = perror "combine"
+combine [f] = f
+combine (f1:f2:fs) = let f = \s -> zipWith (&&) (f1 s) (f2 s) in combine (f:fs)
+
+
+main_server :: Integral a => Opts -> FreqTable a -> [Sequence] -> (String -> String)
+main_server opts ft ss =
+    let sidx     = fromList $ map (\s->(seqlabel s,s)) ss
+        (k,sk,gc,ps) = (kval opts, skip opts,if gap opts > 0 then closegaps (k+gap opts-1) else id, (thresh opts,5,string opts))
+    in (\s -> case Data.Map.lookup (fromStr s) sidx of
+               Nothing -> ("input error: "++s++" not found\n")
+               Just sq -> let cover    = show $ coverage k sk (rcontig k) ft sq
+                              masked_  = show $ mask_generic (lower opts) (if sk==0 then k else k+sk-1) (gc . mask_stat k (skip opts) ps ft) sq
+                                                -- mask_stat (lower opts) k g ps ft sq
+                              unmasked = show $ seqdata sq
+                          in unlines [masked_, cover,unmasked])
+
+\end{code}
+
+Generic masking function.  The list of Bool is a whitelist, so that
+True corresponds to retained nucleotides, while False indicates
+nucleotides to be masked.
+
+(TODO: Run-length encode the boolean list?)
+
+\begin{code}
+
+mask_generic :: MaskWith -> Int -> (Sequence -> [Bool]) -> Sequence -> Sequence
+mask_generic b sz fn s@(Seq l sd mq) = Seq l (fromStr . masked b sz (fn s) . toStr $ sd) mq
+
+\end{code}
+
+Mask all words from a dictionary (i.e. a library of known repeats)
+
+\begin{code}
+
+mask_table :: Integral a => Int -> Int -> FreqTable a -> Sequence -> [Bool]
+mask_table k skp rlib s = let
+    cv = coverage k skp (rcontig k) rlib s -- slight abuse of 'coverage'
+    in map (==0) cv
+
+\end{code}
+
+Masking sequences from frequency counts.
+
+\begin{code}
+
+type MPars = (Double,Double,Double) -- stdevs thres, 1-elim, stringency
+
+-- calculate the 'whitelist'
+mask_stat :: Integral a => Int -> Int -> MPars -> FreqTable a -> Sequence -> [Bool]
+mask_stat k sk (stdevs,b2,strg) ft s =
+    let cv = coverage k sk (rcontig k) ft s
+        ls = sortv $ blunt b2
+	     $ map (\l->(fromIntegral $ head l,length l)) $ group $ sort cv
+        (mu,stdv) = distrib strg ls
+        t  = mu + max b2 (stdevs*stdv)
+     in if null cv || null ls then take (1-k+fromIntegral (seqlength s)) $ repeat True
+        else map ((<=t) . fromIntegral) cv
+
+-- distrib calculates the base distribution
+distrib :: Double -> [(Double,Int)] -> (Double,Double)
+distrib strg ls = distr' ls (uv_mk [])
+    where
+          distr' [] _
+              = perror "distrib"
+          distr' ((mag,cnt):ls') ustd =
+                  let ustd' = uv_add mag cnt ustd
+                      u     = uniVar ustd'
+                      score = variance u / stdev u
+                  in if score >= strg || null ls'
+                     then (mean u,stdev u)
+                     else distr' ls' ustd'
+
+-- s {seqannot = seqannot s
+--              ++[UKV "Threshold" $ printf "%.2f" t]
+--	      ,seqdata = listArray (bounds $ seqdata s) ms}
+
+-- compensate for 1% error rate by reducing number of ones by k%
+-- this is inaccurate, but possibly a workable estimate nonetheless
+blunt :: Double -> [(Double,Int)] -> [(Double,Int)]
+blunt k = reduce1s . dropWhile ((==0).fst)
+   where reduce1s all_@((a,x):ls) = if a==1
+            then (1,max 0 (x-floor k*(sum $ map snd all_) `div` 100)):ls
+            else all_
+         reduce1s [] = [] -- sequence contains no words
+
+-- sort ls around the centre or the modal interval
+sortv :: [(Double,Int)] -> [(Double,Int)]
+sortv ls = let m = scaled_modal ls
+           in sortBy (\(a,_) (c,_) -> compare (abs (a-m)) (abs (c-m))) ls
+
+-- NB: 1 = error - chose mode ignoring 1s? (see C.e. seq. 24)
+
+-- for each window of occurrence frequencies (n..sqrt n), find the one with most positions
+scaled_modal :: [(Double,Int)] -> Double
+scaled_modal = modal . map window . tails'
+
+window :: [(Double,Int)] -> (Double,Int)
+window [] = perror "window"
+window ((mag,cnt):ls) =
+   let top = mag + sqrt mag
+       xs  = (mag,cnt) : takeWhile ((<top).fst) ls
+   in ((mag+(fst . last) xs)/2, sum (map snd xs))
+
+-- tails without the final [[]]
+tails' :: [a] -> [[a]]
+tails' []       = []
+tails' x@(_:xs) = x : tails' xs
+
+{-
+windowize ((mag,count):ls) =
+    let (this,rest) = break ((> mag+sqrt mag) . fst) ls
+    in (mag+sqrt mag,count+sum (map snd this)) : windowize rest
+windowize [] = []
+-}
+
+modal :: Ord b => [(a,b)] -> a
+modal = fst . maximumBy (\x y -> compare (snd x) (snd y))
+
+median :: [(Double,Int)] -> Double
+median ls = let n = (`div` 2) $ sum $ map snd ls
+            in nquant n ls
+
+nquant :: Int -> [(Double,Int)] -> Double
+nquant n ((v,c):vs) | n < c     = v
+                    | otherwise = nquant (n-c) vs
+nquant _ [] = perror "median of an empty list is impossible!"
+
+
+-- faster(?) replacements for Stats.uv and uv_del
+type UV = (Int,Double,Double,Double,Double)
+
+uv_mk :: [(Double,Int)] -> UV
+uv_mk = foldr uv1 (0,0,0,0,0)
+    where uv1 (m,cnt) (n,x,x2,x3,x4) = let c = fromIntegral cnt in
+            (n+cnt,x+m*c,x2+m*m*c,x3+m*m*m*c,x4+m*m*m*m*c)
+
+uv_sub, uv_add :: Double -> Int -> UV -> UV
+uv_sub m cnt (n,x,x2,x3,x4) = let c = fromIntegral cnt in
+    (n-cnt,x-m*c,x2-m*m*c,x3-m*m*m*c,x4-m*m*m*m*c)
+
+uv_add m cnt (n,x,x2,x3,x4) = let c = fromIntegral cnt in
+    (n+cnt,x+m*c,x2+m*m*c,x3+m*m*m*c,x4+m*m*m*m*c)
+
+-- masked takes mask type, kval, threshold, a whitelist ('False' means mask)
+-- and the (nucleotides in the) sequence
+masked :: MaskWith -> Int -> [Bool] -> String -> String
+masked Lower = masked' toLower 0
+masked Ns    = masked' (const 'n') 0
+masked Xs    = masked' (const 'X') 0
+
+-- mask using an arbitrary masking function
+-- the 'ns' parameter keeps track of any remaining masked word, so that
+-- we finish masking out the whole over-represented word
+masked' :: (Char->Char) -> Int -> Int -> [Bool] -> String -> String
+masked' _ _ _ (_:_) [] =
+    perror "masked' (1)"
+masked' mf ns _ [] es = map mf (take ns es) ++ drop ns es
+masked' mf ns k (b:bs) (e:es)
+    | ns == 0 && b  = e    : masked' mf 0 k bs es
+    | ns > 0  && b  = mf e : masked' mf (ns-1) k bs es
+    | not b         = mf e : masked' mf k k bs es
+    | otherwise             = perror "masked' (2)"
+
+-- close gaps of less than 'g' unmasked words between groups of masked characters
+-- 'False' corresponds to words to mask (whitelist)
+closegaps :: Int -> [Bool] -> [Bool]
+closegaps g = close g True . partitions
+
+-- always starts with a (possibly empty) unmasked group
+partitions :: [Bool] -> [Int]
+partitions es = map List.length $ if head es == False then []:partitions' es else partitions' es
+  where partitions' = List.groupBy (\x y -> x == False && y == False || x /= False && y /= False)
+
+-- convert runs of <=g Trues to Falses
+close :: Int -> Bool -> [Int] -> [Bool]
+close _ _    []     = []
+close g True (c:cs) = take c (repeat (if c>g then True else False)) ++ close g False cs
+close g False (c:cs) = (take c $ repeat False) ++ close g True cs
+
+\end{code}
+
+\subsection{Coverage}
+
+Calculating the coverage over a sequence
+
+For sparse indices, this should be a sliding average.
+
+\begin{code}
+
+coverage :: Integral a => Int -> Int -> HashF a -> FreqTable a -> Sequence -> [Int]
+coverage k skp kf tb s = if skp > 0 then sliding_avg skp raw_counts else raw_counts
+       where
+       raw_counts = map mcount $ padkeys (seqlength s-fromIntegral k+1) 0 $ hashes kf $ seqdata s
+       mcount (Just k1) = count tb k1
+       mcount Nothing = 0
+
+padkeys :: Offset -> Offset -> [(k,Offset)] -> [Maybe k]
+padkeys top cur [] = take (fromIntegral (top - cur)) $ repeat Nothing
+padkeys top cur ((k,p):ks) = if cur == p then Just k : padkeys top (cur+1) ks
+                         else Nothing : padkeys top (cur+1) ((k,p):ks)
+
+
+-- or just:
+-- sliding_avg k = map ((`div` k) . sum . take k) . tails
+
+sliding_avg :: Int -> [Int] -> [Int]
+sliding_avg s xs = let x1 = take s xs
+                   in if length x1 == s then slav s (sum x1) xs (drop s xs)
+                   else [sum x1 `div` length x1]
+
+-- todo: use floats?
+slav :: Int -> Int -> [Int] -> [Int] -> [Int]
+slav s tmp hd tl@(_:_) = tmp `div` s : slav s (tmp-head hd+head tl) (tail hd) (tail tl)
+slav s tmp _ [] = [tmp `div` s]
+
+\end{code}
+
diff --git a/RBR/Stats.hs b/RBR/Stats.hs
new file mode 100644
--- /dev/null
+++ b/RBR/Stats.hs
@@ -0,0 +1,136 @@
+{-# LANGUAGE CPP, Rank2Types #-}
+module RBR.Stats (UniVar(..),uniVar,uv,uv_add,uv_del
+              ,Quantiles(..),quantiles) where -- ,histogram,display) where
+
+import Data.List (sort, sortBy, group)
+-- import FiniteMap
+
+class Statistic s where
+    samples :: s -> Int
+
+data UniVar = UV { uv_samples  :: Int
+                 , mean     :: Double
+                 , stdev    :: Double
+                 , variance :: Double
+                 , skewness :: Double
+                 , kurtosis :: Double
+                 , sumSq    :: Double
+                 , coeffVar :: Double
+                 , stdErrMn :: Double
+                 }
+
+instance Statistic UniVar where samples = uv_samples
+
+instance Show UniVar where
+    show u = adjust
+             [["Samples", (show $ samples u)]
+             , ["Mean", (show $ mean u)]
+             , ["Standard dev", (show $ stdev u)]
+             , ["Variance", (show $ variance u)]
+             , ["Skewness", (show $ skewness u)]
+             , ["Kurtosis", (show $ kurtosis u)]
+             , ["Sum of squares", (show $ sumSq u)]
+             , ["Coeff. of var", (show $ coeffVar u)]
+             , ["Std err mean", (show $ stdErrMn u)]
+             ]
+
+adjust :: [[String]] -> String
+adjust [] = []
+adjust ([a,b]:xs) = (a++":"++take (15-length a) (repeat ' ')++b) ++ "\n" ++ adjust xs
+adjust _ = error "Pattern matching error in 'adjust'"
+
+type UVTMP = (Int,Double,Double,Double,Double)
+
+-- | more or less the univariate function from SAS
+--   calculate by tracking n, sum of x, of x², x^3, x^4
+uniVar :: (Int, Double, Double, Double, Double) -> UniVar
+uniVar (n',x,x2,x3,x4) =
+        let n = fromIntegral n'
+            m = x/n
+            m2 = m*m
+            m3 = m*m2
+            var  = (x2-m2*n)/(n-1)
+            s = sqrt(var)
+            skew = (x3 - 3*m*x2 + 2*m3*n)/(s*s*s*n)
+            kurt = (x4 - 4*m*x3 + 6*m2*x2 - 4*m3*x + n*m*m3)/(s*s*s*s*n) - 3
+            cv = s/m
+        in UV n' m s var skew kurt x2 cv (s/sqrt n)
+
+uv_add, uv_del :: Real a => a -> UVTMP -> UVTMP
+uv_add = uv_upd (+)
+uv_del = uv_upd (-)
+
+-- requires -fglasgow-exts
+uv_upd :: (Real a) => (forall b . Real b => b -> b -> b)
+       -> a -> UVTMP -> UVTMP
+uv_upd f d' (n,x,x2,x3,x4) = let d = toFloat d' in n `seq` x `seq` x2
+        `seq` x3 `seq` x4 `seq` (f n 1,f x d,f x2 (d*d),f x3 (d*d*d),f x4 (d*d*d*d))
+    where toFloat = fromRational . toRational
+
+
+uv :: Real a => [a] -> UVTMP
+uv ds = foldr uv_add (0,0,0,0,0) ds
+
+data Quantiles = Qs { wsamples   :: Int
+                 , smallest  :: Double
+                 , quartile1 :: Double
+                 , median    :: Double
+                 , mode      :: [Double]
+                 , quartile3 :: Double
+                 , greatest  :: Double
+                 }
+
+instance Statistic Quantiles where samples = wsamples
+
+instance Show Quantiles where
+    show w = adjust
+             [["Samples", (show $ samples w)]
+             , ["Smallest", (show $ smallest w)]
+             , ["Q1", (show $ quartile1 w)]
+             , ["Median", (show $ median w)]
+             , ["Modes", (show $ mode w)]
+             , ["Q3", (show $ quartile3 w)]
+             , ["Greatest", (show $ greatest w)]]
+
+quantiles :: [Double] -> Quantiles
+quantiles ds = let
+            n = length ds
+            sorted = sort ds
+            q1 = case n `quotRem` 4 of (q,0) -> ((sorted!!(q-1))+(sorted!!q))/2.0
+                                       (q,_) -> sorted!!q
+            q2 = case n `quotRem` 2 of (q,0) -> ((sorted!!(q-1))+(sorted!!q))/2.0
+                                       (q,_) -> sorted!!q
+            q3 = case (3*n) `quotRem` 4 of (q,0) -> ((sorted!!(q-1))+(sorted!!q))/2.0
+                                           (q,_) -> sorted!!q
+            modes = let
+                    ms = sortOn (negate.fst) $ map (\x->(length x,head x))$ group sorted
+                    in (snd $ head ms) : map snd
+                       (takeWhile (\x->fst x==fst (head ms)) (tail ms))
+            in
+            Qs n (head sorted) q1 q2 modes q3 (last sorted)
+
+sortOn :: Ord b => (a->b) -> [a] -> [a]
+sortOn f = sortBy (\x y -> compare (f x) (f y))
+
+{-
+type Histogram = FiniteMap Double Int
+
+-- | histogram builds a histogram given the list of midpoints
+histogram :: [Double] -> [Double] -> Histogram
+histogram ms xs = foldl (insert ms) emptyFM' xs
+    where
+    emptyFM' = foldl (\s v -> addToFM s v 0) emptyFM ms
+    insert :: [Double] -> Histogram -> Double -> Histogram
+    insert (m1:m2:ms) s x = if abs (m1-x) <= abs (m2-x)
+                            then addToFM_C (+) s m1 1 else insert (m2:ms) s x
+    insert [m1] s x = addToFM_C (+) s m1 1
+    insert [] _ _ = error "Must provide at least one midpoint"
+
+-- todo: speed up with strict foldl'
+
+display :: Histogram -> String
+display h = unlines $ map disp1 $ fmToList h
+    where disp1 (v,n) = show v ++ (take (7-(length $ show v)) (repeat ' ')) ++
+                        ": " ++ (take n $ repeat '*')
+
+-}
diff --git a/RBR/TextFormat.lhs b/RBR/TextFormat.lhs
new file mode 100644
--- /dev/null
+++ b/RBR/TextFormat.lhs
@@ -0,0 +1,24 @@
+\begin{code}
+
+-- todo: support uneven column lengths
+
+module RBR.TextFormat where
+
+import Data.List
+
+columns :: String -> [[String]] -> [String]
+columns delim = map (concat . intersperse delim) . transpose . extend
+
+extend :: [[String]] -> [[String]]
+extend cs = zipWith pad (sizes cs) cs
+
+-- mkrow rsizes = concat . intersperse "^" . zipWith pad rsizes
+
+pad :: Int -> [String] -> [String]
+pad n = map (pad1 n)
+    where pad1 o text = text ++ take (o - length text) (repeat ' ')
+
+sizes :: [[String]] -> [Int]
+sizes = map (maximum . map length)
+
+\end{code}
diff --git a/RBR/Util.hs b/RBR/Util.hs
new file mode 100644
--- /dev/null
+++ b/RBR/Util.hs
@@ -0,0 +1,18 @@
+module RBR.Util where
+
+import System.IO
+
+interactl :: String -> (String -> String) -> IO ()
+interactl p f = do
+    putStr p
+    hFlush stdout
+    ls <- getContents
+    i' p f (lines ls)
+
+i' :: String -> (t -> String) -> [t] -> IO ()
+i' _ _ [] = return ()
+i' p f (l:ls) = do
+    putStr (f l)
+    putStr p
+    hFlush stdout
+    i' p f ls
diff --git a/RBR/hooks.c b/RBR/hooks.c
new file mode 100644
--- /dev/null
+++ b/RBR/hooks.c
@@ -0,0 +1,15 @@
+/* after a tip from David Roundy */
+#include <Rts.h>
+#include <RtsFlags.h>
+#include <unistd.h>
+
+void defaultsHook (void) {
+  RtsFlags.GcFlags.maxStkSize  =  8*10000002 / sizeof(W_); /* 80M */
+
+#ifdef _SC_PHYS_PAGES
+  unsigned long long pagesize = sysconf(_SC_PAGESIZE);
+  unsigned long long numpages = sysconf(_SC_PHYS_PAGES);
+  unsigned long long mhs = numpages*pagesize*8/10;
+  RtsFlags.GcFlags.maxHeapSize = 1ULL+mhs/BLOCK_SIZE_W;
+#endif
+}
diff --git a/README b/README
new file mode 100644
--- /dev/null
+++ b/README
@@ -0,0 +1,105 @@
+  New in this version (0.8)
+  -------------------------
+
+Mostly a maintenance release, but at least we have
+
+* Cabalized install
+
+* Sparse mode completed and optimized
+
+* More reasonable default parameters
+
+  Acquiring RBR
+  -------------
+
+I'll try to keep a selection of source and binaries at:
+
+    http://www.ii.uib.no/~ketil/bioinformatics/downloads/software
+
+If you need binaries for other architectures, drop me a mail at
+<ketil@ii.uib.no>.  The latest version should always be available from
+my darcs repo: 
+
+    darcs get http://www.ii.uib.no/~ketil/bioinformatics/repos/rbr
+
+
+  Installation instructions
+  -------------------------
+
+I'm working on a smoother installation process, but this is how it
+currently works.
+
+You need GHC (http://haskell.org/ghc).  Everything is tested against
+version 6.6, but version 6.4.x might also work.  Expect to do some
+modifications in the code for earlier versions than that.
+
+You first need to get my 'bio' library, it is available from the same
+website, and install that.  You can use cabal to install the binary,
+at the top level, do
+
+   chmod +x Setup.hs
+   ./Setup.hs configure  (add --prefix=$HOME if you don't have root access)
+   ./Setup.hs build
+   ./Setup.hs install
+
+If you want to go the more manual route, cd to the src subdirectory, and
+
+    make rbr    -- builds a dynamically linked executable
+or: make rbr_s  -- builds a statically linked executable
+
+The main development platform is Linux/x86, so expect that to be most
+well supported.  In order to build on an aging Sun with an old gcc
+(2.95), I had to comment out 'hooks.o' from the Makefile, and static
+build didn't work either.  I'm investigating this, but perhaps it
+suffices to have a current GCC available, and/or a newer Solaris.
+
+
+  Usage
+  -----
+
+I've no real manual page yet, but 'rbr --help' should list the
+available options.  Basically, masking is determined by examining word
+frequencies of a certain word length (-k), estimating a distribution
+around the "modal interval" of the word frequencies with a certain
+stringency (-s), and masking words with frequencies exceeding the mean
+of this distribution by a certain standard deviations (-t).  Defaults
+are -k 16 -s 1.1 -t 5.0 if -L (lower case masking) is specified and
+-k 16 -s 2.0 -t 8.0 if -n (masking with 'n') is specified.  Lower case
+is now the default.
+
+To mask more agressively, you can either try to reduce stringency, or
+a lower deviation, or both.  Conversely if you want more conservative
+masking.  In general, the differences are small, and typically you can
+compensate for a decrease in one parameter with an increase in the
+other. 
+
+Shorter word length will be more tolerant against SNPs and read
+errors, but increase the variance.  Longer will be less tolarant, but
+have less variance.  In addition, word lengths beyond 16 will be
+slower.
+
+There's also a --sparse=X option that will store a fraction (but at
+least every X'th) of the words.  This will reduce memory consumption proportionally.
+
+RBR's memory usage can be limited with options to the run time
+system's garbage collector.  I good rule of thumb may be to limit it
+to 80-90% of available physcal memory, which will avoid paging to
+disk.  If RBR is compiled with hooks.o linked in, this will be the
+default, but if other behaviour is desired, you can use "+RTS -MxxxM
+-RTS" to limit heap use to xxxMB¹.  See the GHC documentation
+(http://haskell.org/ghc) for more on this.  Usually, you'll get better
+performance by supplying -HxxxM as well (this will reduce GC time,
+again see the GHC docs).
+
+The -v option gives some feedback while RBR runs, which is nice if
+you're using it interactively.
+
+There is also a server mode, where RBR will index a data set, and
+listen on stdin for sequence names, and answer on stdout with the
+original sequence, the masked sequence, and the distribution of word
+frequencies along the sequence.
+
+
+¹) GHC version 6.6 and earlier had a bug that would cause memory
+consumption to be measured incorrectly if the system allocated it in
+an unusual order.  The fix will be in subsequent releases of GHC.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,5 @@
+#!/usr/bin/env runhaskell
+ 
+import Distribution.Simple
+main = defaultMain
+
diff --git a/TODO b/TODO
new file mode 100644
--- /dev/null
+++ b/TODO
@@ -0,0 +1,26 @@
+TODO: Check out options for freqtable data structure.
+      Things to try out: fmindex/afi, hashtable, accumArray, HsJudy
+      And perhaps combining key and count into an Int(|eger|64)?
+
+----------------------------------------
+
+TODO: support shaped keys
+TODO: Repeat ID:
+	output report (.tbl, .out)
+	* calculate 1..k'th order entropy
+	* other?
+
+TODO: Mask against library
+
+TODO: three-pass: build FT, build library, mask against it
+
+TODO: calculate distrib and mask over windows (w=200? 400?)
+   avoids different treatment of different length sequences
+
+- Clustering
+
+- Clustering with (SG/Lee) assembly
+
+ -> statistics to use when clustering:
+    1. mode of word counts distribution (= coverage)
+    2. estimated p value (1-var/mu) (= avg. overlap)
diff --git a/idea.txt b/idea.txt
new file mode 100644
--- /dev/null
+++ b/idea.txt
@@ -0,0 +1,47 @@
+A comprehensive pipeline for EST analysis (?)
+
+IDEA: based on a word map,
+      - identify repeats (RB-style)
+      - mask RB-repeats and against libraries
+      - cluster
+      - assemble
+
+Stages:
+
+  1. FASTA parser (Parsec)
+  2. word map builder (using arrays)
+  3 a) cluster all-against-all
+    b) detect repeats (word frequency aberrances)
+    c) position sequence against a reference
+  4. linear assembly (xtract replacement(?))
+     - and feature output
+
+(thought on RB:
+alt splicing may cause different expression levels along the EST --
+but each exon should give a region of constant expression level,
+possibly tapering off.
+
+ * identify exons and relative expression level?
+ * use region-based "background expression level" to find repeats
+)
+(thought: determine sequence quality based on low match number?)
+
+(thoughts on visualization: align sequences (and curves)?)
+
+(thought: use the length of the sequence to get an idea about
+quality? mail Stephen Rudd, ask what happens if he turns sequences
+around in the quality experiment)
+
+How to handle unkeyable words (i.e. with Ns in them)?
+ - zero (current tack)
+ - average of prec/succ
+ - average of possible interpretations
+
+Call mc something else - mc is midnight commander (or something)
+
+Current status (check darcs!):
+
+f3 - does a bit of debugging output
+rb - does masking based on contiguous words,
+     and can output freqs along a sequence
+mc - compares maskings
diff --git a/rbr.cabal b/rbr.cabal
new file mode 100644
--- /dev/null
+++ b/rbr.cabal
@@ -0,0 +1,44 @@
+Name:           rbr
+Version:        0.8.3
+License:        GPL
+License-File:   LICENSE
+Stability:      Beta
+Author:         Ketil Malde
+Maintainer:     Ketil Malde <ketil@malde.org>
+Category:       Bioinformatics
+Synopsis:       Mask nucleotide (EST) sequences in Fasta format
+Description:    RBR is a tool for masking EST sequences. It uses a
+                statistical model to identify suspicious sequence parts,
+                and masks them. The README has more details.
+                .
+                The Darcs repository is at <http://malde.org/~ketil/rbr>.
+Homepage:       http://malde.org/~ketil/
+
+Cabal-Version:  >= 1.2
+Tested-With:    GHC==6.8.2
+-- add fps for ghc-6.4.2
+Build-Depends:  base, bio >= 0.3.1, QuickCheck, binary, bytestring, containers
+Build-Type:     Simple
+
+data-files:     idea.txt, README, TODO
+
+Library
+        Exposed-Modules:  RBR.Options, RBR.Util, RBR.Stats, RBR.FreqTable, RBR.TextFormat
+        C-sources:        RBR/hooks.c
+        Build-Depends:    base>3, containers, bio
+        Extensions:
+        Ghc-Options:      -Wall -funbox-strict-fields
+
+Executable rbr
+        Executable:     rbr
+        Main-Is:        RBR/RBR.lhs
+        build-depends:  bytestring
+        Extensions:     CPP
+        Ghc-Options:    -Wall -funbox-strict-fields
+
+Executable mct
+        Executable:     mct
+        Main-Is:        RBR/MCT.lhs
+        build-depends:  bytestring
+        Extensions:      BangPatterns, FlexibleContexts
+        Ghc-Options:    -Wall -funbox-strict-fields
