packages feed

tidal-core-1.9.6: src/Sound/Tidal/Chords.hs

module Sound.Tidal.Chords where

{-
    Chords.hs - For .. chords
    Copyright (C) 2020, Alex McLean and contributors

    This library 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 3 of the License, or
    (at your option) any later version.

    This library 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 library.  If not, see <http://www.gnu.org/licenses/>.
-}

import Data.Maybe
import Sound.Tidal.Pattern

-- * Chord definitions

-- ** Major chords

major :: (Num a) => [a]
major = [0, 4, 7]

aug :: (Num a) => [a]
aug = [0, 4, 8]

six :: (Num a) => [a]
six = [0, 4, 7, 9]

sixNine :: (Num a) => [a]
sixNine = [0, 4, 7, 9, 14]

major7 :: (Num a) => [a]
major7 = [0, 4, 7, 11]

major9 :: (Num a) => [a]
major9 = [0, 4, 7, 11, 14]

add9 :: (Num a) => [a]
add9 = [0, 4, 7, 14]

major11 :: (Num a) => [a]
major11 = [0, 4, 7, 11, 14, 17]

add11 :: (Num a) => [a]
add11 = [0, 4, 7, 17]

major13 :: (Num a) => [a]
major13 = [0, 4, 7, 11, 14, 21]

add13 :: (Num a) => [a]
add13 = [0, 4, 7, 21]

-- ** Dominant chords

dom7 :: (Num a) => [a]
dom7 = [0, 4, 7, 10]

dom9 :: (Num a) => [a]
dom9 = [0, 4, 7, 14]

dom11 :: (Num a) => [a]
dom11 = [0, 4, 7, 17]

dom13 :: (Num a) => [a]
dom13 = [0, 4, 7, 21]

sevenFlat5 :: (Num a) => [a]
sevenFlat5 = [0, 4, 6, 10]

sevenSharp5 :: (Num a) => [a]
sevenSharp5 = [0, 4, 8, 10]

sevenFlat9 :: (Num a) => [a]
sevenFlat9 = [0, 4, 7, 10, 13]

nine :: (Num a) => [a]
nine = [0, 4, 7, 10, 14]

eleven :: (Num a) => [a]
eleven = [0, 4, 7, 10, 14, 17]

thirteen :: (Num a) => [a]
thirteen = [0, 4, 7, 10, 14, 17, 21]

-- ** Minor chords

minor :: (Num a) => [a]
minor = [0, 3, 7]

diminished :: (Num a) => [a]
diminished = [0, 3, 6]

minorSharp5 :: (Num a) => [a]
minorSharp5 = [0, 3, 8]

minor6 :: (Num a) => [a]
minor6 = [0, 3, 7, 9]

minorSixNine :: (Num a) => [a]
minorSixNine = [0, 3, 9, 7, 14]

minor7flat5 :: (Num a) => [a]
minor7flat5 = [0, 3, 6, 10]

minor7 :: (Num a) => [a]
minor7 = [0, 3, 7, 10]

minor7sharp5 :: (Num a) => [a]
minor7sharp5 = [0, 3, 8, 10]

minor7flat9 :: (Num a) => [a]
minor7flat9 = [0, 3, 7, 10, 13]

minor7sharp9 :: (Num a) => [a]
minor7sharp9 = [0, 3, 7, 10, 15]

diminished7 :: (Num a) => [a]
diminished7 = [0, 3, 6, 9]

minor9 :: (Num a) => [a]
minor9 = [0, 3, 7, 10, 14]

minor11 :: (Num a) => [a]
minor11 = [0, 3, 7, 10, 14, 17]

minor13 :: (Num a) => [a]
minor13 = [0, 3, 7, 10, 14, 17, 21]

minorMajor7 :: (Num a) => [a]
minorMajor7 = [0, 3, 7, 11]

-- ** Other chords

one :: (Num a) => [a]
one = [0]

five :: (Num a) => [a]
five = [0, 7]

sus2 :: (Num a) => [a]
sus2 = [0, 2, 7]

sus4 :: (Num a) => [a]
sus4 = [0, 5, 7]

sevenSus2 :: (Num a) => [a]
sevenSus2 = [0, 2, 7, 10]

sevenSus4 :: (Num a) => [a]
sevenSus4 = [0, 5, 7, 10]

nineSus4 :: (Num a) => [a]
nineSus4 = [0, 5, 7, 10, 14]

-- ** Questionable chords

sevenFlat10 :: (Num a) => [a]
sevenFlat10 = [0, 4, 7, 10, 15]

nineSharp5 :: (Num a) => [a]
nineSharp5 = [0, 1, 13]

minor9sharp5 :: (Num a) => [a]
minor9sharp5 = [0, 1, 14]

sevenSharp5flat9 :: (Num a) => [a]
sevenSharp5flat9 = [0, 4, 8, 10, 13]

minor7sharp5flat9 :: (Num a) => [a]
minor7sharp5flat9 = [0, 3, 8, 10, 13]

elevenSharp :: (Num a) => [a]
elevenSharp = [0, 4, 7, 10, 14, 18]

minor11sharp :: (Num a) => [a]
minor11sharp = [0, 3, 7, 10, 14, 18]

-- * Chord functions

-- | @chordate cs m n@ selects the @n@th "chord" (a chord is a list of Ints)
-- from a list of chords @cs@ and transposes it by @m@
-- chordate :: Num b => [[b]] -> b -> Int -> [b]
-- chordate cs m n = map (+m) $ cs!!n

-- | @enchord chords pn pc@ turns every note in the note pattern @pn@ into
-- a chord, selecting from the chord lists @chords@ using the index pattern
-- @pc@.  For example, @Chords.enchord [Chords.major Chords.minor] "c g" "0 1"@
-- will create a pattern of a C-major chord followed by a G-minor chord.
-- enchord :: Num a => [[a]] -> Pattern a -> Pattern Int -> Pattern a
-- enchord chords pn pc = flatpat $ (chordate chords) <$> pn <*> pc

-- |
--  The @chordTable@ function outputs a list of all available chords and their
--  corresponding notes. For example, its first entry is @("major",[0,4,7])@ which
--  means that a major triad is formed by the root (0), the major third (4 semitones
--  above the root), and the perfect fifth (7 semitones above the root).
--
--  As the list is big, you can use the function 'chordL'.
--
--  If you know the notes from a chord, but can’t find the name of it, you can use this Haskell code to do a reverse look up into the table:
--
--  > filter (\(_,x)->x==[0,4,7,10]) chordTable
--
--  This will output @[("dom7",[0,4,7,10])]@
--
--  (You’ll need to run @import Sound.Tidal.Chords@ before using this function.)
chordTable :: (Num a) => [(String, [a])]
chordTable =
  [ ("major", major),
    ("maj", major),
    ("M", major),
    ("aug", aug),
    ("plus", aug),
    ("sharp5", aug),
    ("six", six),
    ("6", six),
    ("sixNine", sixNine),
    ("six9", sixNine),
    ("sixby9", sixNine),
    ("6by9", sixNine),
    ("major7", major7),
    ("maj7", major7),
    ("M7", major7),
    ("major9", major9),
    ("maj9", major9),
    ("M9", major9),
    ("add9", add9),
    ("major11", major11),
    ("maj11", major11),
    ("M11", major11),
    ("add11", add11),
    ("major13", major13),
    ("maj13", major13),
    ("M13", major13),
    ("add13", add13),
    ("dom7", dom7),
    ("dom9", dom9),
    ("dom11", dom11),
    ("dom13", dom13),
    ("sevenFlat5", sevenFlat5),
    ("7f5", sevenFlat5),
    ("sevenSharp5", sevenSharp5),
    ("7s5", sevenSharp5),
    ("sevenFlat9", sevenFlat9),
    ("7f9", sevenFlat9),
    ("nine", nine),
    ("eleven", eleven),
    ("11", eleven),
    ("thirteen", thirteen),
    ("13", thirteen),
    ("minor", minor),
    ("min", minor),
    ("m", minor),
    ("diminished", diminished),
    ("dim", diminished),
    ("minorSharp5", minorSharp5),
    ("msharp5", minorSharp5),
    ("mS5", minorSharp5),
    ("minor6", minor6),
    ("min6", minor6),
    ("m6", minor6),
    ("minorSixNine", minorSixNine),
    ("minor69", minorSixNine),
    ("min69", minorSixNine),
    ("minSixNine", minorSixNine),
    ("m69", minorSixNine),
    ("mSixNine", minorSixNine),
    ("m6by9", minorSixNine),
    ("minor7flat5", minor7flat5),
    ("minor7f5", minor7flat5),
    ("min7flat5", minor7flat5),
    ("min7f5", minor7flat5),
    ("m7flat5", minor7flat5),
    ("m7f5", minor7flat5),
    ("minor7", minor7),
    ("min7", minor7),
    ("m7", minor7),
    ("minor7sharp5", minor7sharp5),
    ("minor7s5", minor7sharp5),
    ("min7sharp5", minor7sharp5),
    ("min7s5", minor7sharp5),
    ("m7sharp5", minor7sharp5),
    ("m7s5", minor7sharp5),
    ("minor7flat9", minor7flat9),
    ("minor7f9", minor7flat9),
    ("min7flat9", minor7flat9),
    ("min7f9", minor7flat9),
    ("m7flat9", minor7flat9),
    ("m7f9", minor7flat9),
    ("minor7sharp9", minor7sharp9),
    ("minor7s9", minor7sharp9),
    ("min7sharp9", minor7sharp9),
    ("min7s9", minor7sharp9),
    ("m7sharp9", minor7sharp9),
    ("m7s9", minor7sharp9),
    ("diminished7", diminished7),
    ("dim7", diminished7),
    ("minor9", minor9),
    ("min9", minor9),
    ("m9", minor9),
    ("minor11", minor11),
    ("min11", minor11),
    ("m11", minor11),
    ("minor13", minor13),
    ("min13", minor13),
    ("m13", minor13),
    ("minorMajor7", minorMajor7),
    ("minMaj7", minorMajor7),
    ("mmaj7", minorMajor7),
    ("one", one),
    ("1", one),
    ("five", five),
    ("5", five),
    ("sus2", sus2),
    ("sus4", sus4),
    ("sevenSus2", sevenSus2),
    ("7sus2", sevenSus2),
    ("sevenSus4", sevenSus4),
    ("7sus4", sevenSus4),
    ("nineSus4", nineSus4),
    ("ninesus4", nineSus4),
    ("9sus4", nineSus4),
    ("sevenFlat10", sevenFlat10),
    ("7f10", sevenFlat10),
    ("nineSharp5", nineSharp5),
    ("9sharp5", nineSharp5),
    ("9s5", nineSharp5),
    ("minor9sharp5", minor9sharp5),
    ("minor9s5", minor9sharp5),
    ("min9sharp5", minor9sharp5),
    ("min9s5", minor9sharp5),
    ("m9sharp5", minor9sharp5),
    ("m9s5", minor9sharp5),
    ("sevenSharp5flat9", sevenSharp5flat9),
    ("7s5f9", sevenSharp5flat9),
    ("minor7sharp5flat9", minor7sharp5flat9),
    ("m7sharp5flat9", minor7sharp5flat9),
    ("elevenSharp", elevenSharp),
    ("11s", elevenSharp),
    ("minor11sharp", minor11sharp),
    ("m11sharp", minor11sharp),
    ("m11s", minor11sharp)
  ]

-- | Look up a specific chord: @chordL "minor7"@ returns @(0>1)|[0,3,7,10]@.
chordL :: (Num a) => Pattern String -> Pattern [a]
chordL p = (\name -> fromMaybe [] $ lookup name chordTable) <$> p

-- |
-- Outputs all the available chords:
--
-- @
-- major maj M aug plus sharp5 six 6 sixNine six9 sixby9 6by9 major7 maj7
-- major9 maj9 add9 major11 maj11 add11 major13 maj13 add13 dom7 dom9 dom11
-- dom13 sevenFlat5 7f5 sevenSharp5 7s5 sevenFlat9 7f9 nine eleven 11 thirteen 13
-- minor min m diminished dim minorSharp5 msharp5 mS5 minor6 min6 m6 minorSixNine
-- minor69 min69 minSixNine m69 mSixNine m6by9 minor7flat5 minor7f5 min7flat5
-- min7f5 m7flat5 m7f5 minor7 min7 m7 minor7sharp5 minor7s5 min7sharp5 min7s5
-- m7sharp5 m7s5 minor7flat9 minor7f9 min7flat9 min7f9 m7flat9 m7f9 minor7sharp9
-- minor7s9 min7sharp9 min7s9 m7sharp9 m7s9 diminished7 dim7 minor9 min9 m9
-- minor11 min11 m11 minor13 min13 m13 minorMajor7 minMaj7 mmaj7 one 1 five 5
-- sus2 sus4 sevenSus2 7sus2 sevenSus4 7sus4 nineSus4 ninesus4 9sus4 sevenFlat10
-- 7f10 nineSharp5 9sharp5 9s5 minor9sharp5 minor9s5 min9sharp5 min9s5 m9sharp5
-- m9s5 sevenSharp5flat9 7s5f9 minor7sharp5flat9 m7sharp5flat9 elevenSharp 11s
-- minor11sharp m11sharp m11s
-- @
--
-- (You’ll need to run @import Sound.Tidal.Chords@ before using this function.)
chordList :: String
chordList = unwords $ map fst (chordTable :: [(String, [Int])])

data Modifier = Range Int | Drop Int | Invert | Open deriving (Eq)

instance Show Modifier where
  show (Range i) = "Range " ++ show i
  show (Drop i) = "Drop " ++ show i
  show Invert = "Invert"
  show Open = "Open"

applyModifier :: (Enum a, Num a) => Modifier -> [a] -> [a]
applyModifier (Range i) ds = take i $ concatMap (\x -> map (+ x) ds) [0, 12 ..]
applyModifier Invert [] = []
applyModifier Invert (d : ds) = ds ++ [d + 12]
applyModifier Open ds = case length ds > 2 of
  True -> [(ds !! 0 - 12), (ds !! 2 - 12), (ds !! 1)] ++ reverse (take (length ds - 3) (reverse ds))
  False -> ds
applyModifier (Drop i) ds = case length ds < i of
  True -> ds
  False -> (ds !! s - 12) : (xs ++ drop 1 ys)
  where
    (xs, ys) = splitAt s ds
    s = length ds - i

applyModifierPat :: (Num a, Enum a) => Pattern [a] -> Pattern [Modifier] -> Pattern [a]
applyModifierPat pat modsP = do
  ch <- pat
  ms <- modsP
  return $ foldl (flip applyModifier) ch ms

applyModifierPatSeq :: (Num a, Enum a) => (a -> b) -> Pattern [a] -> [Pattern [Modifier]] -> Pattern [b]
applyModifierPatSeq f pat [] = fmap (map f) pat
applyModifierPatSeq f pat (mP : msP) = applyModifierPatSeq f (applyModifierPat pat mP) msP

chordToPatSeq :: (Num a, Enum a) => (a -> b) -> Pattern a -> Pattern String -> [Pattern [Modifier]] -> Pattern b
chordToPatSeq f noteP nameP modsP = uncollect $ do
  n <- noteP
  name <- nameP
  let ch = map (+ n) (fromMaybe [0] $ lookup name chordTable)
  applyModifierPatSeq f (return ch) modsP

-- | Turns a given pattern of some 'Num' type, a pattern of chord names, and a
-- list of patterns of modifiers into a chord pattern
chord :: (Num a, Enum a) => Pattern a -> Pattern String -> [Pattern [Modifier]] -> Pattern a
chord = chordToPatSeq id