packages feed

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

module Sound.Tidal.Scales (scale, scaleList, scaleTable, getScale, scaleWith, scaleWithList, raiseDegree, lowerDegree, raiseDegrees, lowerDegrees) where

{-
    Scale.hs - Scales for TidalCycles
    Copyright (C) 2020, lvm (Mauro) 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 (fromMaybe)
import Sound.Tidal.Core (slowcat)
import Sound.Tidal.Pattern (Pattern, (<*))
import Sound.Tidal.Utils ((!!!))
import Prelude hiding ((*>), (<*))

-- * Scale definitions

-- ** Five notes scales

minPent :: (Fractional a) => [a]
minPent = [0, 3, 5, 7, 10]

majPent :: (Fractional a) => [a]
majPent = [0, 2, 4, 7, 9]

-- | Another mode of major pentatonic
ritusen :: (Fractional a) => [a]
ritusen = [0, 2, 5, 7, 9]

-- | Another mode of major pentatonic
egyptian :: (Fractional a) => [a]
egyptian = [0, 2, 5, 7, 10]

-- *** Other scales

kumai :: (Fractional a) => [a]
kumai = [0, 2, 3, 7, 9]

hirajoshi :: (Fractional a) => [a]
hirajoshi = [0, 2, 3, 7, 8]

iwato :: (Fractional a) => [a]
iwato = [0, 1, 5, 6, 10]

chinese :: (Fractional a) => [a]
chinese = [0, 4, 6, 7, 11]

indian :: (Fractional a) => [a]
indian = [0, 4, 5, 7, 10]

pelog :: (Fractional a) => [a]
pelog = [0, 1, 3, 7, 8]

-- *** More scales

prometheus :: (Fractional a) => [a]
prometheus = [0, 2, 4, 6, 11]

scriabin :: (Fractional a) => [a]
scriabin = [0, 1, 4, 7, 9]

-- *** Han Chinese pentatonic scales

gong :: (Fractional a) => [a]
gong = [0, 2, 4, 7, 9]

shang :: (Fractional a) => [a]
shang = [0, 2, 5, 7, 10]

jiao :: (Fractional a) => [a]
jiao = [0, 3, 5, 8, 10]

zhi :: (Fractional a) => [a]
zhi = [0, 2, 5, 7, 9]

yu :: (Fractional a) => [a]
yu = [0, 3, 5, 7, 10]

-- ** 6 note scales

whole' :: (Fractional a) => [a]
whole' = [0, 2, 4, 6, 8, 10]

augmented :: (Fractional a) => [a]
augmented = [0, 3, 4, 7, 8, 11]

augmented2 :: (Fractional a) => [a]
augmented2 = [0, 1, 4, 5, 8, 9]

-- *** Hexatonic modes with no tritone

hexMajor7 :: (Fractional a) => [a]
hexMajor7 = [0, 2, 4, 7, 9, 11]

hexDorian :: (Fractional a) => [a]
hexDorian = [0, 2, 3, 5, 7, 10]

hexPhrygian :: (Fractional a) => [a]
hexPhrygian = [0, 1, 3, 5, 8, 10]

hexSus :: (Fractional a) => [a]
hexSus = [0, 2, 5, 7, 9, 10]

hexMajor6 :: (Fractional a) => [a]
hexMajor6 = [0, 2, 4, 5, 7, 9]

hexAeolian :: (Fractional a) => [a]
hexAeolian = [0, 3, 5, 7, 8, 10]

-- ** 7 note scales

major :: (Fractional a) => [a]
major = [0, 2, 4, 5, 7, 9, 11]

ionian :: (Fractional a) => [a]
ionian = [0, 2, 4, 5, 7, 9, 11]

dorian :: (Fractional a) => [a]
dorian = [0, 2, 3, 5, 7, 9, 10]

phrygian :: (Fractional a) => [a]
phrygian = [0, 1, 3, 5, 7, 8, 10]

lydian :: (Fractional a) => [a]
lydian = [0, 2, 4, 6, 7, 9, 11]

mixolydian :: (Fractional a) => [a]
mixolydian = [0, 2, 4, 5, 7, 9, 10]

aeolian :: (Fractional a) => [a]
aeolian = [0, 2, 3, 5, 7, 8, 10]

minor :: (Fractional a) => [a]
minor = [0, 2, 3, 5, 7, 8, 10]

locrian :: (Fractional a) => [a]
locrian = [0, 1, 3, 5, 6, 8, 10]

harmonicMinor :: (Fractional a) => [a]
harmonicMinor = [0, 2, 3, 5, 7, 8, 11]

harmonicMajor :: (Fractional a) => [a]
harmonicMajor = [0, 2, 4, 5, 7, 8, 11]

melodicMinor :: (Fractional a) => [a]
melodicMinor = [0, 2, 3, 5, 7, 9, 11]

melodicMinorDesc :: (Fractional a) => [a]
melodicMinorDesc = [0, 2, 3, 5, 7, 8, 10]

melodicMajor :: (Fractional a) => [a]
melodicMajor = [0, 2, 4, 5, 7, 8, 10]

bartok :: (Fractional a) => [a]
bartok = melodicMajor

hindu :: (Fractional a) => [a]
hindu = melodicMajor

-- *** Raga modes

todi :: (Fractional a) => [a]
todi = [0, 1, 3, 6, 7, 8, 11]

purvi :: (Fractional a) => [a]
purvi = [0, 1, 4, 6, 7, 8, 11]

marva :: (Fractional a) => [a]
marva = [0, 1, 4, 6, 7, 9, 11]

bhairav :: (Fractional a) => [a]
bhairav = [0, 1, 4, 5, 7, 8, 11]

ahirbhairav :: (Fractional a) => [a]
ahirbhairav = [0, 1, 4, 5, 7, 9, 10]

-- *** More modes

superLocrian :: (Fractional a) => [a]
superLocrian = [0, 1, 3, 4, 6, 8, 10]

romanianMinor :: (Fractional a) => [a]
romanianMinor = [0, 2, 3, 6, 7, 9, 10]

hungarianMinor :: (Fractional a) => [a]
hungarianMinor = [0, 2, 3, 6, 7, 8, 11]

neapolitanMinor :: (Fractional a) => [a]
neapolitanMinor = [0, 1, 3, 5, 7, 8, 11]

enigmatic :: (Fractional a) => [a]
enigmatic = [0, 1, 4, 6, 8, 10, 11]

spanish :: (Fractional a) => [a]
spanish = [0, 1, 4, 5, 7, 8, 10]

-- *** Modes of whole tones with added note ->

leadingWhole :: (Fractional a) => [a]
leadingWhole = [0, 2, 4, 6, 8, 10, 11]

lydianMinor :: (Fractional a) => [a]
lydianMinor = [0, 2, 4, 6, 7, 8, 10]

neapolitanMajor :: (Fractional a) => [a]
neapolitanMajor = [0, 1, 3, 5, 7, 9, 11]

locrianMajor :: (Fractional a) => [a]
locrianMajor = [0, 2, 4, 5, 6, 8, 10]

-- ** 8 note scales

diminished :: (Fractional a) => [a]
diminished = [0, 1, 3, 4, 6, 7, 9, 10]

diminished2 :: (Fractional a) => [a]
diminished2 = [0, 2, 3, 5, 6, 8, 9, 11]

-- ** Modes of limited transposition

messiaen1 :: (Fractional a) => [a]
messiaen1 = whole'

messiaen2 :: (Fractional a) => [a]
messiaen2 = diminished

messiaen3 :: (Fractional a) => [a]
messiaen3 = [0, 2, 3, 4, 6, 7, 8, 10, 11]

messiaen4 :: (Fractional a) => [a]
messiaen4 = [0, 1, 2, 5, 6, 7, 8, 11]

messiaen5 :: (Fractional a) => [a]
messiaen5 = [0, 1, 5, 6, 7, 11]

messiaen6 :: (Fractional a) => [a]
messiaen6 = [0, 2, 4, 5, 6, 8, 10, 11]

messiaen7 :: (Fractional a) => [a]
messiaen7 = [0, 1, 2, 3, 5, 6, 7, 8, 9, 11]

-- ** Arabic maqams taken from SuperCollider's Scale.sc

bayati :: (Fractional a) => [a]
bayati = [0, 1.5, 3, 5, 7, 8, 10]

hijaz :: (Fractional a) => [a]
hijaz = [0, 1, 4, 5, 7, 8.5, 10]

sikah :: (Fractional a) => [a]
sikah = [0, 1.5, 3.5, 5.5, 7, 8.5, 10.5]

rast :: (Fractional a) => [a]
rast = [0, 2, 3.5, 5, 7, 9, 10.5]

iraq :: (Fractional a) => [a]
iraq = [0, 1.5, 3.5, 5, 6.5, 8.5, 10.5]

saba :: (Fractional a) => [a]
saba = [0, 1.5, 3, 4, 6, 8, 10]

-- ** 12 note scales

chromatic :: (Fractional a) => [a]
chromatic = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]

-- |
--  Interprets a pattern of note numbers into a particular named scale. For example:
--
--  > d1
--  >   $ jux rev
--  >   $ chunk 4 (fast 2 . (|- n 12))
--  >   $ off 0.25 (|+ 7)
--  >   $ struct (iter 4 "t(5,8)")
--  >   $ n (scale "ritusen" "0 .. 7")
--  >   # sound "superpiano"
scale :: (Fractional a) => Pattern String -> Pattern Int -> Pattern a
scale = getScale scaleTable

-- |
--  Build a scale function, with additional scales if you wish. For example:
--
--  > let myscale =
--  >   getScale
--  >     ( scaleTable ++
--  >         [ ("techno", [0,2,3,5,7,8,10])
--  >         , ("broken", [0,1,4,7,8,10])
--  >         ]
--  >     )
--
--  The above takes the standard 'scaleTable' as a starting point and adds two custom scales to it. You’ll be able to use the new function in place of the normal one:
--
--  > d1 $ n (myscale "techno" "0 1 2 3 4 5 6 7") # sound "superpiano"
getScale :: (Fractional a) => [(String, [a])] -> Pattern String -> Pattern Int -> Pattern a
getScale table sp p =
  ( \n scaleName ->
      noteInScale (fromMaybe [0] $ lookup scaleName table) n
  )
    <$> p
    <* sp
  where
    octave s x = x `div` length s
    noteInScale s x = (s !!! x) + fromIntegral (12 * octave s x)

{- Variant of @scale@ allowing to modify the current scale (seen as a list) with an [a] -> [a] function.

These are equivalent:

> d1 $ up (scaleWith "major" (insert 1) $ run 8) # s "superpiano"
> d1 $ up "0 1 2 4 5 7 9 11" # s "superpiano"

-}
scaleWith :: (Eq a, Fractional a) => Pattern String -> ([a] -> [a]) -> Pattern Int -> Pattern a
scaleWith = getScaleMod scaleTable

{- Variant of @scaleWith@ providing a list of modifier functions instead of a single function
-}
scaleWithList :: (Eq a, Fractional a) => Pattern String -> ([[a] -> [a]]) -> Pattern Int -> Pattern a
scaleWithList sp fs p = slowcat $ map (\f -> scaleWith sp f p) fs

{- Variant of @getScale@ used to build the @scaleWith@ function
-}
getScaleMod :: (Eq a, Fractional a) => [(String, [a])] -> Pattern String -> ([a] -> [a]) -> Pattern Int -> Pattern a
getScaleMod table sp f p =
  ( \n scaleName ->
      noteInScale (uniq $ f $ fromMaybe [0] $ lookup scaleName table) n
  )
    <$> p
    <* sp
  where
    octave s x = x `div` length s
    noteInScale s x = (s !!! x) + fromIntegral (12 * octave s x)

{- Eliminates duplicates in a sorted list
-}
uniq :: (Eq a) => [a] -> [a]
uniq (h1 : h2 : tl) = if (h1 == h2) then h1 : (uniq tl) else h1 : (uniq (h2 : tl))
uniq l = l

{- Raises a specified degree of a scale, provided as a numbers list.
Meant to be passed as an argument to @scaleWith@
-}
raiseDegree :: (Fractional a) => Int -> [a] -> [a]
raiseDegree _ (hd : []) = (hd + 1) : []
raiseDegree 0 (hd : tl) = (hd + 1) : tl
raiseDegree n (hd : tl) = hd : (raiseDegree (n - 1) tl)
raiseDegree _ [] = error "Degree is not present in the scale"

{- Lowers a specified degree of a scale, provided as a numbers list.
Meant to be passed as an argument to @scaleWith@
-}
lowerDegree :: (Fractional a) => Int -> [a] -> [a]
lowerDegree _ (hd : []) = (hd - 1) : []
lowerDegree 0 (hd : tl) = (hd - 1) : tl
lowerDegree n (hd : tl) = hd : (lowerDegree (n - 1) tl)
lowerDegree _ [] = error "Degree is not present in the scale"

{- Like @raiseDegree@, but raises a range of degrees instead of a single one
-}
raiseDegrees :: (Fractional a) => Int -> Int -> [a] -> [a]
raiseDegrees _ _ (hd : []) = (hd + 1) : []
raiseDegrees 0 0 (hd : tl) = (hd + 1) : tl
raiseDegrees 0 m (hd : tl) = (hd + 1) : (raiseDegrees 0 (m - 1) tl)
raiseDegrees n m (hd : tl) = hd : (raiseDegrees (n - 1) (m - 1) tl)
raiseDegrees _ _ [] = error "Degrees are out of the scale"

{- Like @lowerDegree@, but lowers a range of degrees instead of a single one
-}
lowerDegrees :: (Fractional a) => Int -> Int -> [a] -> [a]
lowerDegrees _ _ (hd : []) = (hd - 1) : []
lowerDegrees 0 0 (hd : tl) = (hd - 1) : tl
lowerDegrees 0 m (hd : tl) = (hd - 1) : (lowerDegrees 0 (m - 1) tl)
lowerDegrees n m (hd : tl) = hd : (lowerDegrees (n - 1) (m - 1) tl)
lowerDegrees _ _ [] = error "Degrees are out of the scale"

-- |
--  Outputs this list of all the available scales:
--
-- @
-- minPent majPent ritusen egyptian kumai hirajoshi iwato chinese indian pelog
-- prometheus scriabin gong shang jiao zhi yu whole wholetone augmented augmented2
-- hexMajor7 hexDorian hexPhrygian hexSus hexMajor6 hexAeolian major ionian dorian
-- phrygian lydian mixolydian aeolian minor locrian harmonicMinor harmonicMajor
-- melodicMinor melodicMinorDesc melodicMajor bartok hindu todi purvi marva bhairav
-- ahirbhairav superLocrian romanianMinor hungarianMinor neapolitanMinor enigmatic
-- spanish leadingWhole lydianMinor neapolitanMajor locrianMajor diminished
-- octatonic diminished2 octatonic2 messiaen1 messiaen2 messiaen3 messiaen4
-- messiaen5 messiaen6 messiaen7 chromatic bayati hijaz sikah rast saba iraq
-- @
scaleList :: String
scaleList = unwords $ map fst (scaleTable :: [(String, [Rational])])

-- |
--  Outputs a list of all available scales and their corresponding notes. For
--  example, its first entry is @("minPent",[0,3,5,7,10]@) which means that
--  a minor pentatonic scale is formed by the root (0), the minor third (3 semitones
--  above the root), the perfect fourth (5 semitones above the root), etc.
--
--  As the list is big, you can use the Haskell function lookup to look up a
--  specific scale: @lookup "phrygian" scaleTable@. This will output
--  @Just [0.0,1.0,3.0,5.0,7.0,8.0,10.0]@.
--
--  You can also do a reverse lookup into the scale table. For example:
--
--  > filter ( \(_, x) -> take 3 x == [0,2,4] ) scaleTable
--
--  The above example will output all scales of which the first three notes are
--  the root, the major second (2 semitones above the fundamental), and the major
--  third (4 semitones above the root).
scaleTable :: (Fractional a) => [(String, [a])]
scaleTable =
  [ ("minPent", minPent),
    ("majPent", majPent),
    ("ritusen", ritusen),
    ("egyptian", egyptian),
    ("kumai", kumai),
    ("hirajoshi", hirajoshi),
    ("iwato", iwato),
    ("chinese", chinese),
    ("indian", indian),
    ("pelog", pelog),
    ("prometheus", prometheus),
    ("scriabin", scriabin),
    ("gong", gong),
    ("shang", shang),
    ("jiao", jiao),
    ("zhi", zhi),
    ("yu", yu),
    ("whole", whole'),
    ("wholetone", whole'),
    ("augmented", augmented),
    ("augmented2", augmented2),
    ("hexMajor7", hexMajor7),
    ("hexDorian", hexDorian),
    ("hexPhrygian", hexPhrygian),
    ("hexSus", hexSus),
    ("hexMajor6", hexMajor6),
    ("hexAeolian", hexAeolian),
    ("major", major),
    ("ionian", ionian),
    ("dorian", dorian),
    ("phrygian", phrygian),
    ("lydian", lydian),
    ("mixolydian", mixolydian),
    ("aeolian", aeolian),
    ("minor", minor),
    ("locrian", locrian),
    ("harmonicMinor", harmonicMinor),
    ("harmonicMajor", harmonicMajor),
    ("melodicMinor", melodicMinor),
    ("melodicMinorDesc", melodicMinorDesc),
    ("melodicMajor", melodicMajor),
    ("bartok", bartok),
    ("hindu", hindu),
    ("todi", todi),
    ("purvi", purvi),
    ("marva", marva),
    ("bhairav", bhairav),
    ("ahirbhairav", ahirbhairav),
    ("superLocrian", superLocrian),
    ("romanianMinor", romanianMinor),
    ("hungarianMinor", hungarianMinor),
    ("neapolitanMinor", neapolitanMinor),
    ("enigmatic", enigmatic),
    ("spanish", spanish),
    ("leadingWhole", leadingWhole),
    ("lydianMinor", lydianMinor),
    ("neapolitanMajor", neapolitanMajor),
    ("locrianMajor", locrianMajor),
    ("diminished", diminished),
    ("octatonic", diminished),
    ("diminished2", diminished2),
    ("octatonic2", diminished2),
    ("messiaen1", messiaen1),
    ("messiaen2", messiaen2),
    ("messiaen3", messiaen3),
    ("messiaen4", messiaen4),
    ("messiaen5", messiaen5),
    ("messiaen6", messiaen6),
    ("messiaen7", messiaen7),
    ("chromatic", chromatic),
    ("bayati", bayati),
    ("hijaz", hijaz),
    ("sikah", sikah),
    ("rast", rast),
    ("saba", saba),
    ("iraq", iraq)
  ]