hmt-0.20: Music/Theory/Z/Boros_1990.hs
-- | James Boros. "Some Properties of the All-Trichord Hexachord".
-- _In Theory Only_, 11(6):19--41, 1990.
module Music.Theory.Z.Boros_1990 where
import Data.Char {- base -}
import Data.List {- base -}
import Data.Maybe {- base -}
import Numeric {- base -}
import qualified Data.Graph.Inductive.Graph as G {- fgl -}
import qualified Data.Graph.Inductive.Basic as G {- fgl -}
import qualified Data.Graph.Inductive.PatriciaTree as G {- fgl -}
import qualified Data.Graph.Inductive.Query.BFS as G {- fgl -}
import qualified Music.Theory.Array.Text as T
import qualified Music.Theory.Combinations as T
import qualified Music.Theory.Graph.Dot as T
import qualified Music.Theory.Graph.Fgl as T
import qualified Music.Theory.List as T
import qualified Music.Theory.Set.List as T
import qualified Music.Theory.Tuple as T
import qualified Music.Theory.Z as T
import qualified Music.Theory.Z.Forte_1973 as T
import qualified Music.Theory.Z.Tto as T
-- * Util
singular :: String -> [t] -> t
singular err l =
case l of
[x] -> x
_ -> error ("not singular: " ++ err)
set_eq :: Ord t => [t] -> [t] -> Bool
set_eq p q = T.set p == T.set q
elem_by :: (t -> t -> Bool) -> t -> [t] -> Bool
elem_by f e = any (f e)
-- * Tto
tto_tni_univ :: Integral i => [T.Tto i]
tto_tni_univ = filter ((== 1) . T.tto_M) (T.z_tto_univ 5 T.z12)
all_tn :: Integral i => [i] -> [[i]]
all_tn p = map (\n -> map (T.z_add T.z12 n) p) [0..11]
all_tni :: Integral i => [i] -> [[i]]
all_tni p = map (\f -> T.z_tto_apply T.z12 f p) tto_tni_univ
uniq_tni :: Integral i => [i] -> [[i]]
uniq_tni = nub . all_tni
type Pc = Int
type Pcset = [Pc]
type Sc = Pcset
-- > pcset_trs 3 [0,1,9] == [0,3,4]
pcset_trs :: Int -> Pcset -> Pcset
pcset_trs = T.z_tto_tn T.z12
-- | Forte prime forms of the twelve trichordal set classes.
--
-- > length trichords == 12
trichords :: [Pcset]
trichords = filter ((== 3) . length) (T.z_sc_univ T.z12)
-- | Is a pcset self-inversional, ie. is the inversion of /p/ a transposition of /p/.
--
-- > map (\p -> (p,self_inv p)) trichords
self_inv :: Pcset -> Bool
self_inv p = elem_by set_eq (map (T.z_negate T.z12) p) (all_tn p)
-- | Pretty printer, comma separated.
--
-- > pcset_pp [0,3,7,10] == "0,3,7,10"
pcset_pp :: Pcset -> String
pcset_pp = intercalate "," . map show
-- | Pretty printer, hexadecimal, no separator.
--
-- > pcset_pp_hex [0,3,7,10] == "037A"
pcset_pp_hex :: Pcset -> String
pcset_pp_hex = map toUpper . concatMap (`showHex` "")
-- * Ath
-- | Forte prime form of the all-trichord hexachord.
--
-- > T.sc_name ath == "6-Z17"
-- > T.sc "6-Z17" == ath
ath :: Pcset
ath = [0,1,2,4,7,8]
-- | Is /p/ an instance of 'ath'.
is_ath :: Pcset -> Bool
is_ath p = T.z_forte_prime T.z12 p == ath
-- | Table 1, p.20
--
-- > length ath_univ == 24
ath_univ :: [Pcset]
ath_univ = uniq_tni ath
-- | Calculate 'T.Tto' of pcset, which must be an instance of 'ath'.
--
-- > ath_tni [1,2,3,7,8,11] == T.Tto 3 1 True
ath_tni :: Pcset -> T.Tto Pc
ath_tni = singular "ath_tni" . filter ((== 1) . T.tto_M) . T.z_tto_rel 5 T.z12 ath
-- | Give label for instance of 'ath', prime forms are written H and inversions h.
--
-- > ath_pp [1,2,3,7,8,11] == "h3"
ath_pp :: Pcset -> String
ath_pp p =
let r = ath_tni p
h = if T.tto_I r then 'h' else 'H'
in h : show (T.tto_T r)
-- | The twenty three-element subsets of 'ath'.
--
-- > length ath_trichords == 20
ath_trichords :: [Pcset]
ath_trichords = T.combinations (3::Int) ath
-- | '\\' of 'ath' and /p/, ie. the pitch classes that are in 'ath' and not in /p/.
--
-- > ath_complement [0,1,2] == [4,7,8]
ath_complement :: Pcset -> Pcset
ath_complement p = ath \\ p
-- | /p/ is a pcset, /q/ a sc, calculate pcsets in /q/ that with /p/ form 'ath'.
--
-- > ath_completions [0,1,2] (T.sc "3-3") == [[6,7,10],[4,7,8]]
-- > ath_completions [6,7,10] (T.sc "3-5") == [[1,2,8]]
ath_completions :: Pcset -> Sc -> [Pcset]
ath_completions p q =
let f z = is_ath (p ++ z)
in filter f (uniq_tni q)
realise_ath_seq :: [Pcset] -> [[Pcset]]
realise_ath_seq sq =
case sq of
p:q:sq' -> concatMap (\z -> map (p :) (realise_ath_seq (z : sq'))) (ath_completions p q)
_ -> [sq]
-- return edges that connect z to nodes at gr in an ATH relation
ath_gr_extend :: [T.Edge Pcset] -> Pcset -> [T.Edge Pcset]
ath_gr_extend gr c =
let f x y = if is_ath (x ++ y) then Just (x,y) else Nothing
g (p,q) = mapMaybe (f c) [p,q]
in nub (map T.t2_sort (concatMap g gr))
gr_trs :: Int -> [T.Edge Pcset] -> [T.Edge Pcset]
gr_trs n = let f (p,q) = (pcset_trs n p,pcset_trs n q) in map f
-- * Tables
-- > length table_3 == 20
table_3 :: [((Pcset,Sc,T.SC_Name),(Pcset,Sc,T.SC_Name))]
table_3 =
let f p = let q = ath_complement p
i x = (x,T.z_forte_prime T.z12 x,T.sc_name x)
in (i p,i q)
in map f ath_trichords
pp_tbl :: T.Text_Table -> [String]
pp_tbl = T.table_pp T.table_opt_simple
-- > putStrLn $ unlines $ table_3_md
table_3_md :: [String]
table_3_md =
let pp = pcset_pp_hex
f ((p,q,r),(s,t,u)) = [pp p,pp q,r,pp s,pp t,u]
hdr = ["P","P/SC","P/F","Q=H0-P","Q/SC","Q/F"]
in pp_tbl (hdr : map f table_3)
-- > length table_4 == 10
table_4 :: [((Pcset,Pcset,T.SC_Name),(Pcset,Pcset,T.SC_Name))]
table_4 = nub (map T.t2_sort table_3)
-- > putStrLn $ unlines $ table_4_md
table_4_md :: [String]
table_4_md =
let pp = pcset_pp_hex
f ((p,q,r),(s,t,u)) = [pp p ++ "/" ++ pp s,pp q ++ "/" ++ pp t,r ++ "/" ++ u]
hdr = ["Trichords","Prime Forms","Forte Numbers"]
in pp_tbl (hdr : map f table_4)
table_5 :: [(Pcset,Int)]
table_5 = T.histogram (map (T.z_forte_prime T.z12) ath_trichords)
-- > putStrLn $ unlines $ table_5_md
table_5_md :: [String]
table_5_md =
let f (p,q) = [pcset_pp_hex p,show q]
in pp_tbl (["SC","#ATH"] : map f table_5)
table_6 :: [(Pcset,Int,Int)]
table_6 =
let f (p,n) = (p,n,length (filter (\q -> p `T.is_subset` q) ath_univ))
in map f table_5
-- > putStrLn $ unlines $ table_6_md
table_6_md :: [String]
table_6_md =
let f (p,q,r) = [pcset_pp_hex p,show q,show r]
in pp_tbl (["SC","#H0","#Hn"] : map f table_6)
-- * Figures
fig_1 :: [T.Edge Pcset]
fig_1 = map (T.t2_map T.p3_snd) table_4
fig_1_gr :: G.Gr Pcset ()
fig_1_gr = T.g_from_edges fig_1
-- > putStrLn $ unlines $ map (unwords . map pcset_pp) fig_2
fig_2 :: [[Pcset]]
fig_2 =
let g = G.undir fig_1_gr
n = G.labNodes g
n' = filter ((== 2) . G.deg g . fst) n
c = T.combinations (2::Int) n'
p = map (\l -> let (lhs,rhs) = T.firstSecond l in G.esp (fst lhs) (fst rhs) g) c
p' = filter (not . null) p
in map (mapMaybe (`lookup` n)) p'
fig_3 :: [[T.Edge Pcset]]
fig_3 = map (concatMap (T.adj2 1) . realise_ath_seq) fig_2
fig_3_gr :: [G.Gr Pcset ()]
fig_3_gr = map T.g_from_edges fig_3
fig_4 :: [[T.Edge Pcset]]
fig_4 =
let p = concatMap realise_ath_seq fig_2
q = filter ([0,1,2] `elem`) p
in map (T.adj2 1) q
fig_5 :: [[T.Edge Pcset]]
fig_5 =
let c = [0,4,8]
f gr = case ath_gr_extend gr c of
[] -> Nothing
r -> Just (gr ++ r)
g0 = concat fig_4
in mapMaybe (\n -> f (gr_trs n g0)) [0 .. 11]
-- * Drawing
uedge_set :: Ord v => [T.Edge v] -> [T.Edge v]
uedge_set = nub . map T.t2_sort
-- | Self-inversional pcsets are drawn in a double circle, other pcsets in a circle.
set_shape :: Pcset -> T.Dot_Attr
set_shape v = ("shape",if self_inv v then "doublecircle" else "circle")
type Gr = G.Gr Pcset ()
gr_pp' :: (Pcset -> String) -> T.Graph_Pp Pcset ()
gr_pp' f = (\(_,v) -> [set_shape v,("label",f v)],const [])
gr_pp :: T.Graph_Pp Pcset ()
gr_pp = gr_pp' pcset_pp
d_fig_1 :: [String]
d_fig_1 = T.fgl_to_udot [] gr_pp fig_1_gr
d_fig_3_g :: Gr
d_fig_3_g = T.g_from_edges (uedge_set (concat fig_3))
d_fig_3 :: [String]
d_fig_3 = T.fgl_to_udot [] gr_pp d_fig_3_g
d_fig_3' :: [[String]]
d_fig_3' = map (T.fgl_to_udot [("node:shape","circle")] gr_pp) fig_3_gr
d_fig_4_g :: Gr
d_fig_4_g = T.g_from_edges (uedge_set (concat fig_4))
d_fig_4 :: [String]
d_fig_4 = T.fgl_to_udot [] gr_pp d_fig_4_g
d_fig_5_g :: Gr
d_fig_5_g = T.g_from_edges (uedge_set (concat fig_5))
d_fig_5 :: [String]
d_fig_5 = T.fgl_to_udot [("edge:len","1.5")] (gr_pp' pcset_pp_hex) d_fig_5_g
d_fig_5_e :: [T.Edge_Lbl Pcset Pcset]
d_fig_5_e = map (\(p,q) -> ((p,q),p++q)) (uedge_set (concat fig_5))
d_fig_5_g' :: G.Gr Pcset Pcset
d_fig_5_g' = T.g_from_edges_l d_fig_5_e
d_fig_5' :: [String]
d_fig_5' =
let pp = (const [("shape","")],\(_,e) -> [("label",ath_pp e)])
in T.fgl_to_udot [("node:shape","point"),("edge:len","1.25")] pp d_fig_5_g'