% from AutoTrack by Stefan Ratschan \begin{haskelllisting}
> module Haskore.Interface.AutoTrack.ChordChart
>           (T(Cons), bars, hasChord,
>            length, concat) where
> import qualified Haskore.Music        as Music
> import qualified Haskore.Interface.AutoTrack.ChartBar as ChartBar
> import qualified Haskore.Interface.AutoTrack.Transposeable as Transposeable
> import qualified Haskore.Basic.Duration as Dur
> import           Haskore.Basic.Duration (wn, (%+), )
> import           Data.Char(isSpace)
> import qualified Data.List as List
> import Prelude hiding (length, concat)
\end{haskelllisting} Chord charts are lists of bars. They have the following input syntax: \begin{verbatim} chart = { (bar | '%') '|' } \end{verbatim} The character '\%' is a shortcut for the same bar as before. Comments can occur everywhere in the text. They start with "--" and continue till the end of the current line. \begin{haskelllisting}
> data T = Cons {bars :: [ ChartBar.T ] } deriving Show
> length :: Integral a => T -> a
> length = fromIntegral . List.length . bars
> instance Read T where
>   readsPrec _ = Haskore.Interface.AutoTrack.ChordChart.read
> concat :: T -> T -> T 
> concat (Cons x) (Cons y) = Cons (x++y)
> read :: ReadS T
> read s = read1 (ChartBar.Cons wn []) (filterComment s)
> filterComment :: String -> String
> filterComment ('-':'-':r) = filterComment (tail (snd (break (=='\n') r)))
> filterComment (c:r) = (c:filterComment r)
> filterComment "" = ""
> read1 :: ChartBar.T -> ReadS T
> read1 lb (c:r) | isSpace c = 
>     read1 lb (dropWhile isSpace r)
> read1 lb ('%':r) = 
>     [ (Cons (lb:br), r2) |
>         ('|':r1) <- [ dropWhile isSpace r ],
>         (Cons br, r2) <- read1 lb r1 ]
> read1 (ChartBar.Cons sig _) s@(_:_) =
>     [ (Cons (b:br), r1) |
>         (b, ('|':r)) <- ChartBar.readChordSymbol sig Nothing s,
>         (Cons br, r1) <- read1 b r ]
> read1 _ s = [ (Cons [], s) ]
\end{haskelllisting} Chord charts can be transposed. \begin{haskelllisting}
> instance Transposeable.C T where
>   transpose i (Cons c) = Cons (fmap (Transposeable.transpose i) c)
\end{haskelllisting} We can extract a Boolean list from a chord chart that tells whether there is a chord at a certain position (hc[i] is true iff d*i has a chord). \begin{haskelllisting}
> hasChord :: T -> (Music.Dur, [ Bool ] )
> hasChord c =
>    let g = barGCD c
>    in (g, hasChord1 g c)
> hasChord1 :: Music.Dur -> T -> [ Bool ]
> hasChord1 bDur (Cons c) = List.concat (map (hasChordBar bDur) c)
> barUnit :: ChartBar.T -> Music.Dur -> Dur.Ratio
> barUnit bar d = d * (1 %+ ChartBar.length bar)
> hasChordBar :: Music.Dur -> ChartBar.T -> [ Bool ]
> hasChordBar bDur bar@(ChartBar.Cons d chords) =
>    let times =
>           fromInteger
>              (Dur.divide (barUnit bar d) bDur)
>        createList = replicate times . maybe False (const True)
>    in  concatMap createList chords
> barGCD :: T -> Music.Dur
> barGCD (Cons c) =
>    let chordDur bar = barUnit bar (ChartBar.dur bar)
>    in  foldr1 Dur.gcd (map chordDur c)
\end{haskelllisting}