packages feed

HarmTrace-Base-1.6.0.0: src/Tests.hs

{-# LANGUAGE FlexibleInstances          #-}
{-# OPTIONS_GHC -fno-warn-orphans       #-}
--------------------------------------------------------------------------------
-- |
-- Module      :  HarmTrace.Base.Chord.Tests
-- Copyright   :  (c) 2012--2016, Chordify BV
-- License     :  LGPL-3
--
-- Maintainer  :  haskelldevelopers@chordify.net
-- Stability   :  experimental
-- Portability :  non-portable
--
-- Summary: Defines some property tests for testing the HarmTrace.Base package
--------------------------------------------------------------------------------
module Main where

import HarmTrace.Base.Chord
import HarmTrace.Base.Parse   ( parseDataSafe, pChord )
import HarmTrace.Base.Time

import Test.QuickCheck
import Test.QuickCheck.Test   ( isSuccess )

import Data.List              ( sort, foldl' )

import System.Random          ( Random )
import System.Exit            ( exitFailure, exitSuccess )
import Control.Monad          ( when )

instance Arbitrary DiatonicNatural where
  arbitrary = elements . enumFrom $ C

instance Arbitrary IntNat where
  arbitrary = elements . enumFrom $ I1

instance Arbitrary Accidental where
  arbitrary = elements [Nat,Sh,Fl,SS,FF]


instance Arbitrary Root where
  arbitrary = elements . map pcToRoot $ [0..11]

instance Arbitrary Interval where
  arbitrary = choose (0,21) >>= return . icToInterval

-- instance Arbitrary a => Arbitrary (Note a) where
  -- arbitrary = do nat <- arbitrary
                 -- acc <- arbitrary
                 -- return (Note acc nat)


instance Arbitrary Shorthand where
  arbitrary = elements . enumFrom $ Maj

instance Arbitrary Addition where
  arbitrary = oneof [ arbitrary >>= return . Add
                    , arbitrary >>= return . NoAdd ]

instance Arbitrary a => Arbitrary (Chord a) where
  arbitrary = do r   <- arbitrary
                 sh  <- elements [Maj, Min, Aug, Dim, Maj7, Min7, Sev, Dim7, HDim7, MinMaj7]
                 -- sh  <- arbitrary
                 -- add <- arbitrary >>= listOf . return . Add

                 b   <- arbitrary
                 return (Chord r sh [] b ) -- (Note Nat I1))

instance Arbitrary a => Arbitrary (Timed a) where
  arbitrary = do x  <- arbitrary
                 s  <- elements [2 .. 5]
                 ts <- vector s -- guarantee that this list has a minimum of 2 items
                 return . Timed x . sort $ ts

data ChkTimed = ChkTimed MeterKind [Timed ChordLabel] deriving (Show, Eq)

instance Arbitrary ChkTimed where
  arbitrary = do let -- Step function for creating a Timed ChordLabel
                     f ::Fractional t => [Timed' t a] -> (a, [t]) -> [Timed' t a]
                     f _     (_,[]) = error "should not happen"
                     f [   ] (a, x) = [Timed a (map Time (0:x))]
                     f (h:t) (a, x) = let o   = offset h
                                          g y = Time (y + o)
                                      in Timed a (map g (0:x)) : h : t

                     -- creates additional duplicates at random places
                     -- dups ds ["a","b"] might return ["a", "a", "b"]
                     dups :: [Bool] -> [a] -> [a]
                     dups [ ]     l     = l
                     dups _      [ ]    = []
                     dups (b:bs) (e:es) | b         = e : dups bs (e : es)
                                        | otherwise = e : dups bs es

                 ds <- arbitrary  -- [Bool]
                 as <- arbitrary >>= return . dups ds -- chords including duplicates

                 -- e.g. Gen [[1.2,3.4], [2.2]] etc.
                 ns <- arbitrary >>= return . map (sort . map abs)
                                 >>= return . filter (\x -> let l = length x in l > 0 && l <= 4)

                 mk <- arbitrary -- MeterKind
                 bt <- elements [One, Two, Three, Four]

                 return . ChkTimed mk . updateBeats mk bt
                        . reverse . foldl' f [] $ zip as ns

instance (Random t, Fractional t) => Arbitrary (BeatTime t) where
  arbitrary = do choose (0.0, 100.0) >>= return . Time

instance Arbitrary MeterKind where
  arbitrary = elements [Duple, Triple]

instance Arbitrary Beat where
  arbitrary = elements [One, Two, Three, Four, NoBeat]

instance Arbitrary Mode where
  arbitrary = elements [MajMode, MinMode]

instance Arbitrary Key where
  arbitrary = Key <$> arbitrary <*> arbitrary  
  
pcProp :: Root -> Bool
pcProp r = (toPitchClass r) == toPitchClass (pcToRoot (toPitchClass r))

pcSetProp :: Chord Root -> Bool
pcSetProp c = c == toChord (chordRoot c) (toIntSet c) (chordBass c)

intervalProp :: Interval -> Bool
intervalProp i = i == icToInterval (toIntervalClss i)

-- intervalProp2 :: Int -> Bool
-- intervalProp2 i = i == toIntervalClss (icToInterval i)

enHarEqProp :: Root -> Bool
enHarEqProp a = a &== a

parseProp :: Chord Root -> Bool
parseProp c = parseDataSafe pChord (show c) == c

-- N.B. this test passes if you limit the inversions to intervals within one 
-- octave.
parseNoteInversionProp :: Chord Root -> Bool
parseNoteInversionProp c = parseDataSafe pChord (showChordWithNoteInversion c) == c

mergeTimedTest, mergeTimedTest2, mergeTimedTest3, mergeTimedTest4 :: ChkTimed -> Bool
mergeTimedTest (ChkTimed _ cs) = expandTimed (mergeTimed cs) == expandTimed cs
mergeTimedTest2 (ChkTimed _ cs) = expandTimed (expandTimed cs) == expandTimed cs
mergeTimedTest3 (ChkTimed _ cs) = mergeTimed (mergeTimed cs) == mergeTimed cs
mergeTimedTest4 (ChkTimed _ cs) = mergeTimed (expandTimed cs) == mergeTimed cs

meterKind1, meterKind2 :: ChkTimed -> Bool
meterKind1 (ChkTimed Duple  cs) = mergeTimed (setMeterKind Duple  cs) == mergeTimed cs
meterKind1 (ChkTimed Triple cs) = mergeTimed (setMeterKind Triple cs) == mergeTimed cs

meterKind2 (ChkTimed Duple  cs) = setMeterKind Duple  cs == expandTimed cs
meterKind2 (ChkTimed Triple cs) = setMeterKind Triple cs == expandTimed cs

-- meterKind1 (ChkTimed Duple  cs) = setMeterKind Duple  (setMeterKind Triple cs) == cs
-- meterKind1 (ChkTimed Triple cs) = setMeterKind Triple (setMeterKind Duple  cs) == cs

correctNextBeat :: ChkTimed -> Bool
correctNextBeat (ChkTimed mk cs) = and . map (correctBeatTimes mk . getTimeStamps) $ cs

correctBeatTimes :: Fractional t => MeterKind -> [BeatTime t] -> Bool
correctBeatTimes _  [ ] = True
correctBeatTimes _  [_] = True
correctBeatTimes mk (a:b:tl) = beat b == nextBeat mk (beat a) && correctBeatTimes mk tl

correctNextBeatMK :: (MeterKind, ChkTimed) -> Bool
correctNextBeatMK (mk, ChkTimed _ cs) = correctNextBeat
                                      . ChkTimed mk . mergeTimed . setMeterKind mk $ cs


keyShowRead :: Key -> Bool
keyShowRead k = (read $ show k) == k

--------------------------------------------------------------------------------
-- Execute the tests
--------------------------------------------------------------------------------

main :: IO ()
main = do let myTest :: Testable p => String -> [p] -> IO ()
              myTest s p = do putStrLn (" *** Testing HarmTrace-Base: "++ s ++": ... ")
                              rs <- mapM verboseCheckResult p
                              when (not . all isSuccess $ rs) exitFailure

          myTest "roots"        [ pcProp, enHarEqProp ]
          myTest "chords"       [ pcSetProp, parseProp ]
          myTest "intervals I"  [ intervalProp ]
          -- myTest "intervals II" [ intervalProp2 ]
          myTest "mergeTimed"   [ mergeTimedTest, mergeTimedTest2, mergeTimedTest3, mergeTimedTest4 ]
          myTest "nextBeat"     [ correctNextBeat ]
          myTest "meterKind"    [ meterKind1, meterKind2 ]
          myTest "meterKind II" [ correctNextBeatMK ]
          myTest "keyShowRead"  [ keyShowRead ]
          exitSuccess