-----------------------------------------------------------------------------
-- |
-- Module      :  Mezzo.Render.Transform
-- Description :  Score-level transformations
-- Copyright   :  (c) Dima Szamozvancev
-- License     :  MIT
--
-- Maintainer  :  ds709@cam.ac.uk
-- Stability   :  experimental
-- Portability :  portable
--
-- Functions and combinators for transforming Scores. Allows for more flexibility,
-- but no static correctness guarantees.
--
-----------------------------------------------------------------------------

module Mezzo.Render.Transform
    (transpose, delay, (><), (+++), flatten, reprise, cascade, scale, volta)
    where

import Mezzo.Render.Score
import Mezzo.Render.MIDI

import Codec.Midi hiding (key, Key)
import qualified Codec.Midi as CM (key, Key)
import Prelude hiding (min)
import Control.Arrow (first, second)

-- | Transpose a single MIDI message by the given number of semitones.
transposeMessage :: Int -> Message -> Message
transposeMessage t (NoteOff ch key vel) = NoteOff ch (key + t) vel
transposeMessage t (NoteOn ch key vel) = NoteOn ch (key + t) vel
transposeMessage _ m = m

-- | Transpose a score by the given number of semitones.
transpose :: Int -> Score -> Score
transpose n = map (second (transposeMessage n))

-- | Delay a score by the given number of ticks (60ths of a thirty-second note).
delay :: Ticks -> Score -> Score
delay ticks (n@(_, NoteOn{}) : rest) =
    [ (0, NoteOn {channel = 0, CM.key = 60, velocity = 0})
    , (ticks, NoteOn {channel = 0, CM.key = 60, velocity = 0})
    ] ++ n : rest
delay ticks (m : rest) = m : delay ticks rest

-- | Remove the attribute MIDI messages in the score header.
stripHeader :: Score -> Score
stripHeader [] = []
stripHeader (n@(_, NoteOn{}) : rest) = n : rest
stripHeader (_ : rest) = stripHeader rest

-- | Concatenation of scores.
(+++) :: Score -> Score -> Score
s1 +++ s2 = s1 ++ stripHeader s2

-- | Flatten a list of scores into a single score.
flatten :: [Score] -> Score
flatten = foldr (+++) []

-- | Repeat a score the given number of times.
reprise :: Int -> Score -> Score
reprise n = flatten . replicate n

-- | Repeat the score, successively applying the function to each repetition.
-- cascade 4 f s = s +++ f s +++ f (f s) +++ f (f (f s))
cascade :: Int -> (Score -> Score) -> Score -> Score
cascade n f = flatten . take n . iterate f

-- | Create a scale of the given length and interval between scores.
scale :: Int -> Int -> Score -> Score
scale l i = cascade l (transpose i)

-- | Repeat the score with the different endings at each repetition.
volta :: Score -> [Score] -> Score
volta s vs = flatten $ map (s +++) vs