uku-0.0.1.0: src/Uku/Render.hs
{-|
Description : Rendering module for Ukulele fingering charts.
This module provides functionality
to render Ukulele fingering charts as ANSI art.
-}
module Uku.Render where
import Protolude as P (
Applicative (pure),
Bounded,
Either (..),
Enum (fromEnum),
Eq (..),
Foldable (fold, foldr, length, maximum),
Functor (fmap),
Int,
Map,
Num ((+), (-)),
Ord,
Semigroup ((<>)),
Show,
Text,
Traversable (mapM),
intercalate,
intersperse,
maybeToEither,
otherwise,
replicate,
transpose,
($),
(&),
(<$>),
)
import Data.List.Index (imap, setAt)
import Data.Map.Strict as Map (fromList, lookup)
import Data.Text as Text (intercalate, toLower)
import Uku.GeneralTypes (
Interval (..),
MidiNote (..),
)
data Finger = Thumb | Index | Middle | Ring | Pinky | AnyFinger
deriving (Eq, Ord, Show)
fingerToText :: Finger -> Text
fingerToText finger =
let colorize text = "\x1b[31m" <> text <> "\x1b[0m"
in colorize $ case finger of
Thumb -> "T"
Index -> "I"
Middle -> "M"
Ring -> "R"
Pinky -> "P"
AnyFinger -> "●"
data FretPosition
= F1
| F2
| F3
| F4
| F5
| F6
| F7
| F8
| F9
| FA
| FB
| FC
| FD
| FE
| FF
| FG
| FH
| FI
| FJ
| FK
| FL
| FM
| FN
| FO
| FP
| FQ
| FR
| FS
| FT
| FU
| FV
| FW
| FX
| FY
| FZ
deriving (Bounded, Enum, Eq, Ord, Show)
data Pick
= Mute
| Open
| Pick FretPosition Finger
deriving (Eq, Ord, Show)
pickToInt :: Pick -> Int
pickToInt fretPosition =
case fretPosition of
Pick fret _ -> fromEnum fret + 1
_ -> 0
type Fretting = [[Pick]]
type InstStrings = [Interval]
data PlayedInstrument = PlayedInst InstStrings MidiNote Fretting
type Instrument = Fretting -> PlayedInstrument
ukulele :: Instrument
ukulele =
PlayedInst [I07, I00, I04, I09] M34
-- TODO: Remove duplication
mapChordToHarmonicEquivalent :: Text -> Text
mapChordToHarmonicEquivalent chord = case chord of
"ab" -> "g#"
"bb" -> "a#"
"db" -> "c#"
"eb" -> "d#"
"gb" -> "f#"
"abm" -> "g#m"
"bbm" -> "a#m"
"dbm" -> "c#m"
"ebm" -> "d#m"
"gbm" -> "f#m"
"ab7" -> "g#7"
"bb7" -> "a#7"
"db7" -> "c#7"
"eb7" -> "d#7"
"gb7" -> "f#7"
_ -> chord
chordToPlayedInsts :: Text -> Instrument -> Either Text [PlayedInstrument]
chordToPlayedInsts chord instrument =
let
maybeInst = do
frettings <-
archaicToFretting
& Map.lookup (chord & toLower & mapChordToHarmonicEquivalent)
pure $ fmap instrument frettings
errorMessage =
"There is no fretting available for the specified chord"
in
maybeToEither errorMessage maybeInst
putPickOnString :: Pick -> [Text] -> [Text]
putPickOnString pick stringParts =
case pick of
Mute -> stringParts
Open -> stringParts
(Pick _ finger) ->
setAt
(pickToInt pick)
(fingerToText finger)
stringParts
getString :: Int -> Int -> Int -> [Pick] -> [Text]
getString numberOfFrets numOfStrings stringIndex strPick =
let
openString =
[ if
| stringIndex == 0 -> "╒"
| stringIndex == (numOfStrings - 1) -> "╕"
| otherwise -> "╤"
]
<> P.replicate (numberOfFrets + 1) "│"
in
P.foldr putPickOnString openString strPick
showFretting :: Fretting -> Text
showFretting fretting =
let
maxPos = P.maximum (pickToInt <$> fold fretting)
in
fretting
& imap (getString maxPos $ P.length fretting)
& P.intersperse (["═"] <> P.replicate (maxPos + 1) "_")
& P.transpose
& P.intercalate ["\n"]
& fold
& (<> "\n")
showPlayedInst :: PlayedInstrument -> Either Text Text
showPlayedInst (PlayedInst strings _ fretting)
| P.length strings /= P.length fretting =
Left "Number of strings and picks in fretting do not match"
| otherwise =
Right $
showFretting fretting
getAnsiArts :: Text -> Either Text Text
getAnsiArts chord = do
playedInsts <- chordToPlayedInsts chord ukulele
fmap
(Text.intercalate "\n")
(mapM showPlayedInst playedInsts)
archaicToFretting :: Map Text [Fretting]
archaicToFretting =
Map.fromList
[
( "a"
,
[ [[Pick F2 Middle], [Pick F1 Index], [Open], [Open]]
,
[ [Pick F4 Index, Pick F6 Ring]
, [Pick F4 Index]
, [Pick F4 Index, Pick F5 Middle]
, [Pick F4 Index]
]
]
)
, ("am", [[[Pick F2 Middle], [Open], [Open], [Open]]])
, ("a7", [[[Open], [Pick F1 Index], [Open], [Open]]])
,
( "a#"
,
[
[ [Pick F1 Index, Pick F3 Ring]
, [Pick F1 Index, Pick F2 Middle]
, [Pick F1 Index]
, [Pick F1 Index]
]
]
)
,
( "a#m"
,
[
[ [Pick F1 Index, Pick F3 Ring]
, [Pick F1 Index]
, [Pick F1 Index]
, [Pick F1 Index]
]
]
)
,
( "a#7"
,
[
[ [Pick F1 Index]
, [Pick F1 Index, Pick F2 Middle]
, [Pick F1 Index]
, [Pick F1 Index]
]
]
)
,
( "b"
,
[
[ [Pick F2 Index, Pick F4 Ring]
, [Pick F2 Index, Pick F3 Middle]
, [Pick F2 Index]
, [Pick F2 Index]
]
]
)
,
( "bm"
,
[
[ [Pick F2 Index, Pick F4 Ring]
, [Pick F2 Index]
, [Pick F2 Index]
, [Pick F2 Index]
]
]
)
, ("b7", [[[Pick F2 Index], [Pick F2 Index], [Pick F2 Index], [Open]]])
, ("c", [[[Open], [Open], [Open], [Pick F3 Ring]]])
, ("cm", [[[Open], [Pick F3 Index], [Pick F3 Index], [Pick F3 Index]]])
, ("c7", [[[Open], [Open], [Open], [Pick F1 Index]]])
,
( "c#"
,
[
[ [Pick F1 Index]
, [Pick F1 Index]
, [Pick F1 Index]
, [Pick F1 Index, Pick F4 Pinky]
]
]
)
, ("c#m", [[[Pick F1 Index], [Pick F1 Index], [Open], [Open]]])
,
( "c#7"
,
[
[ [Pick F1 Index]
, [Pick F1 Index]
, [Pick F1 Index]
, [Pick F1 Index, Pick F2 Middle]
]
]
)
, ("d", [[[Pick F2 Index], [Pick F2 Middle], [Pick F2 Middle], [Open]]])
, ("dm", [[[Pick F2 Middle], [Pick F2 Ring], [Pick F1 Index], [Open]]])
,
( "d7"
,
[
[ [Pick F2 Index]
, [Pick F2 Index]
, [Pick F2 Index]
, [Pick F2 Index, Pick F3 Middle]
]
]
)
, ("d#", [[[Open], [Pick F3 Ring], [Pick F3 Pinky], [Pick F1 Index]]])
,
( "d#m"
,
[ [[Pick F3 Ring], [Pick F3 Pinky], [Pick F2 Middle], [Pick F1 Index]]
]
)
,
( "d#7"
,
[
[ [Pick F3 Index]
, [Pick F3 Index]
, [Pick F3 Index]
, [Pick F3 Index, Pick F4 Middle]
]
]
)
, ("e", [[[Pick F1 Index], [Pick F4 Pinky], [Open], [Pick F2 Middle]]])
, ("em", [[[Open], [Pick F4 Ring], [Pick F3 Middle], [Pick F2 Index]]])
, ("e7", [[[Pick F1 Index], [Pick F2 Middle], [Open], [Pick F2 Pinky]]])
, ("f", [[[Pick F2 Middle], [Open], [Pick F1 Index], [Open]]])
, ("fm", [[[Pick F1 Index], [Open], [Pick F1 Middle], [Pick F3 Ring]]])
,
( "f7"
,
[
[ [Pick F2 Middle]
, [Pick F3 Ring]
, [Pick F1 Index]
, [Pick F4 Pinky]
]
,
[ [Pick F5 Index]
, [Pick F5 Index]
, [Pick F5 Index]
, [Pick F5 Index, Pick F6 Middle]
]
]
)
,
( "f#"
,
[
[ [Pick F1 Index, Pick F3 Ring]
, [Pick F1 Index]
, [Pick F1 Index, Pick F2 Middle]
, [Pick F1 Index]
]
]
)
, ("f#m", [[[Pick F2 Middle], [Pick F1 Index], [Pick F2 Ring], [Open]]])
,
( "f#7"
,
[
[ [Pick F3 Middle]
, [Pick F4 Ring]
, [Pick F2 Index]
, [Pick F4 Pinky]
]
]
)
, ("g", [[[Open], [Pick F2 Index], [Pick F3 Ring], [Pick F2 Middle]]])
, ("gm", [[[Open], [Pick F2 Middle], [Pick F3 Ring], [Pick F1 Index]]])
, ("g7", [[[Open], [Pick F2 Middle], [Pick F1 Index], [Pick F2 Ring]]])
,
( "g#"
,
[
[ [Pick F3 Index, Pick F5 Ring]
, [Pick F3 Index]
, [Pick F3 Index, Pick F4 Middle]
, [Pick F3 Index]
]
]
)
,
( "g#m"
,
[ [[Pick F4 Ring], [Pick F3 Middle], [Pick F4 Pinky], [Pick F2 Index]]
]
)
,
( "g#7"
,
[
[ [Pick F1 Index]
, [Pick F3 Ring]
, [Pick F2 Middle]
, [Pick F3 Pinky]
]
]
)
]