packages feed

AlgoRhythm-0.1.0.0: test/TMusic.hs

{-# LANGUAGE PostfixOperators    #-}
{-# LANGUAGE ScopedTypeVariables #-}
module TMusic where

import Control.Arrow                        ((>>>))
import Test.Framework                       (Test, testGroup)
import Test.Framework.Providers.HUnit       (testCase)
import Test.Framework.Providers.QuickCheck2 (testProperty)
import Test.HUnit                           ((@?=))
import Test.QuickCheck                      ((==>))

import GenSetup
import Music

musicTests :: Test
musicTests = testGroup "Music"
  [ testGroup "Instances"
      [ testCase "Functor" $
          const (D#5) <$> line [C#4<|qn, D#2<|wn] @?= line [D#5<|qn, D#5<|wn]
      , testCase "Foldable" $
          let f a = [a]
          in  foldMap f (line [C#4<|qn, D#2<|wn]) @?= [C#4, D#2]
      ]
  , testGroup "Transpose"
      [ testCase "a pitch class" $
          C ~> M3 @?= E
      , testCase "a pitch" $
          C#4 ~> M7 @?= B#4
      , testCase "a note" $
          C#4 <|hn ~> M3 @?= E#4<|hn
      , testCase "a chord" $
          let a  = chord $ C#4=|maj <|| def
              a' = chord $ C#5=|maj <|| def
          in  a ~> P8 @?= a'
      , testCase "a sequence of chords" $
          let a  = chord $ C#4=|maj7 <|| 1%8
              a' = chord $ Cs#4=|maj7 <|| 1%8
              b  = chord $ Ds#4=|aug <|| def
              b' = chord $ E#4=|aug  <|| def
          in  line [a, b, a] ~> Mi2 @?= line [a', b', a']
      , testCase "a scale" $
          let a  = scale $ C#4+|minor <|| def
              a' = scale $ B#4+|minor <|| def
          in  a ~> M7 @?= a'
      , testCase "a sequence of scales" $
          let a  = line $ C#4+|blues <|| 1%8
              a' = line $ Cs#4+|blues <|| 1%8
              b  = line $ Ds#4+|harmonicMinor <|| def
              b' = line $ E#4+|harmonicMinor  <|| def
          in  line [a, b, a] ~> Mi2 @?= line [a', b', a']
      , testProperty "identityUp" $ \(p :: Pitch) (d :: Duration) ->
          p<|d ~> P1 == p<|d
      , testProperty "identityDown" $ \(p :: Pitch) (d :: Duration) ->
          p<|d <~ P1 == p<|d
      , testProperty "commutativeUp" $
          \(p :: Pitch) (m :: Interval) (n :: Interval) (d :: Duration) ->
            ((~> m) >>> (~> n)) (p<|d) == ((~> n) >>> (~> m)) (p<|d)
      , testProperty "commutativeDown" $
          \(p :: Pitch) (m :: Interval) (n :: Interval) (d :: Duration) ->
            ((<~ m) >>> (<~ n)) (p<|d) == ((<~ n) >>> (<~ m)) (p<|d)
      , testProperty "erasure" $
          \(p :: Pitch) (m :: Interval) (d :: Duration) ->
            fromEnum p + fromEnum m <= fromEnum (maxBound :: Pitch) ==>
            ((~> m) >>> (<~ m)) (p<|d) == (p<|d)
      ]
  , testGroup "Invert"
      [ testCase "absolute pitches" $
          invert ([0, 10, -20] :: [AbsPitch]) @?= [0, -10, 20]
      , testCase "a melody" $
          let melody  = line [C#4<|hn, E#2<|wn, C#3<|en]
              melody' = line [C#4<|hn, Gs#5<|wn, C#5<|en]
          in  invert melody @?= melody'
      , testCase "a chord" $
          invert maj7 @?= [P1, Mi3, P5, Mi6]
      , testProperty "a diminished chord" $ \n -> n > 0 ==>
          invertN n dim7 == dim7
      , testCase "a scale" $
          mode vi ionian @?= minor
      , testProperty "scale orbit" $ do
          sc <- genScale
          return $ or [invertN n sc == sc | n <- [5..9]]
      , testProperty "chord orbit" $ do
          ch <- genChord
          return $ length ch < 5 ==> or [invertN n ch == ch | n <- [4, 5]]
      ]
  , testGroup "Retro"
      [ testCase "a melody" $
          (line [C#4<|hn, (wn~~), Gs#4<|en] ><) @?=
            line [Gs#4<|en, (wn~~), C#4<|hn]
      , testCase "a chord" $
          (chord (C#4=|maj <||wn) ><) @?= chord (C#4=|maj <||wn)
      , testCase "a scale" $
          (scale (C#4+|major <||sn) ><) @?=
            line (reverse [C, D, E, F, G, A, B]<#4<||sn)
      ]
  , testGroup "Repeat"
      [ testCase "a single note" $
          let note = C#4<|wn
          in  4 ## note @?= note :+: note :+: note :+: note
      , testCase "a piece of music" $
          let piece = line $ chord <$> [c, c', c']
              c = Cs#4=|maj7 <|| def
              c' = db#3=|m7b5 <|| def
          in  3 ## piece @?= piece :+: piece :+: piece
      ]
  , testGroup "Scaling time"
      [ testCase "to smaller single duration" $
          2 *~ hn @?= qn
      , testCase "to bigger single duration" $
          1%2 *~ sn @?= en
      , testCase "a melody" $
          1%4 *~ C#4<|en :+: C#3<|sn @?= C#4<|hn :+: C#3<|qn
      , testCase "a chord" $
          4 *~ chord (C#4=|maj <||wn) @?= chord (C#4=|maj <||qn)
      , testCase "a scale" $
          1%4 *~ scale (eb#2+|bebopDorian <||qn) @?= scale (eb#2=|bebopDorian <||wn)
      ]
  , testGroup "Other"
      [ testCase "toList" $
          musicToList (C#4<|hn :+: (wn~~) :+: C#5<|qn) @?=
            [(Just $ C#4, hn), (Nothing, wn), (Just $ C#5, qn)]
      , testCase "fromList" $
          listToMusic [(Just $ C#4, hn), (Nothing, wn), (Just $ C#5, qn)] @?=
            (C#4<|hn :+: (wn~~) :+: C#5<|qn)
      , testCase "normalize" $
          let m = (wn~~) :: Melody
          in  normalize ((m :+: m) :+: m) @?= m :+: m :+: m
      ]
  ]