{- |
Create chord patterns with controlable level of details.
-}
module Haskore.Example.Detail where

import qualified Haskore.Basic.Pitch as Pitch
import Haskore.Basic.Pitch (Class(..))
import qualified Haskore.Melody as Melody
import qualified Haskore.Music.GeneralMIDI as MidiMusic
import qualified Haskore.Music             as Music

import qualified System.Random as Random

import System.Random (RandomGen, randomR, mkStdGen, )
import Control.Monad.State (State(State), evalState, )

import Haskore.General.Utility (toMaybe, )
import qualified Data.List as List



levels :: [[Pitch.T]]
levels =
   ((0,C) : []) :
   ((0,C) : (1,C) : []) :
   ((0,C) : (1,C) : (0,G) : []) :
   ((0,C) : (1,C) : (0,G) : (0,E) : []) :
   ((0,C) : (1,C) : (0,G) : (0,E) : (0,D) : (0,F) : []) :
   []


{-
candidate for Utility

cf. Data.MarkovChain.randomItem
-}
randomItem :: (RandomGen g) => [a] -> State g a
randomItem x = fmap (x!!) (randomRState (0, length x - 1))

{- |
'System.Random.randomR' wrapped in a State monad.
-}
randomRState :: (RandomGen g) => (Int,Int) -> State g Int
randomRState bnds = State (randomR bnds)


merge :: [a] -> [a] -> [a]
merge xs ys =
   concat (zipWith (\x y -> [x,y]) xs ys)



dyadicPattern :: [Pitch.T]
dyadicPattern =
   foldl1 merge $
   zipWith
      (\g level -> flip evalState g (sequence (repeat (randomItem level))))
      (List.unfoldr (Just . Random.split) (mkStdGen 925)) $
   levels


simpleSong :: MidiMusic.T
simpleSong =
   Music.changeTempo 2 $
   Music.take 10 $
   MidiMusic.fromMelodyNullAttr MidiMusic.AcousticGrandPiano $
   MidiMusic.line $
   List.map (\p -> Melody.note p MidiMusic.sn ()) dyadicPattern



dyadicLevelPattern :: [(Int, Pitch.T)]
dyadicLevelPattern =
   foldl1 merge $
   zipWith3
      (\g i level -> map ((,) i) $
          flip evalState g (sequence (repeat (randomItem level))))
      (List.unfoldr (Just . Random.split) (mkStdGen 925))
      [0..] $
   levels


song :: MidiMusic.T
song =
   Music.changeTempo 2 $
   MidiMusic.fromMelodyNullAttr MidiMusic.AcousticGrandPiano $
   MidiMusic.line $
   List.map (maybe MidiMusic.snr (\p -> Melody.note p MidiMusic.sn ())) $
   List.zipWith
      (\li (l,p) -> toMaybe (l<=li) p)
      (concatMap (replicate (2 * 2 ^ length levels)) [0 .. length levels]) $
   dyadicLevelPattern