module Mida.Midi
( genMidi
, topDefs )
where
import Control.Monad.State.Strict
import Data.Foldable (foldl')
import Data.List (zipWith7)
import Data.Maybe (listToMaybe)
import Prelude hiding (mod)
import qualified Codec.Midi as M
import Mida.Language (MidaEnv, setRandGen, evalDef)
data Batch = Batch
{ btDur :: [Int]
, btVel :: [Int]
, btPch :: [Int]
, _btMod :: Maybe [Int]
, _btBth :: Maybe [Int]
, _btAft :: Maybe [Int]
, _btBnd :: Maybe [Int] }
infixl 4 <!>
(<!>) :: ([Int] -> [Int]) -> Batch -> Batch
f <!> (Batch d v p m t a b) =
Batch (f d) (f v) (f p) (f <$> m) (f <$> t) (f <$> a) (f <$> b)
data ModParams = ModParams
{ mpValue :: Int
, mpFigure :: Int
, mpDuration :: Int
, mpChannel :: Int
, mpProducer :: Int -> Int -> M.Message
, mpUpBounds :: (Int, Int)
, mpDnBounds :: (Int, Int) }
modP :: ModParams
modP = ModParams
{ mpValue = 0
, mpFigure = 0
, mpDuration = 0
, mpChannel = 0
, mpProducer = flip M.ControlChange 1
, mpUpBounds = (0x0000, 0x007f)
, mpDnBounds = (0x007f, 0x0000) }
bthP :: ModParams
bthP = modP { mpProducer = flip M.ControlChange 2 }
aftP :: ModParams
aftP = modP { mpProducer = M.ChannelPressure }
bndP :: ModParams
bndP = modP
{ mpProducer = M.PitchWheel
, mpUpBounds = (0x2000, 0x3fff)
, mpDnBounds = (0x2000, 0x0000) }
type Track = M.Track Int
genMidi :: Monad m => Int -> Int -> Int -> MidaEnv m M.Midi
genMidi s q b = do
setRandGen s
voices <- filter defined <$> mapM request [0..mvIndex]
return M.Midi { M.fileType = M.MultiTrack
, M.timeDiv = M.TicksPerBeat q
, M.tracks = zipWith (toTrack . slice (b * q)) voices [0..] }
request :: Monad m => Int -> MidaEnv m Batch
request n = do
dur <- eval' defDur
vel <- eval' defVel
pch <- eval' defPch
mod <- eval' defMod
bth <- eval' defBth
aft <- eval' defAft
bnd <- eval' defBnd
return $ Batch dur vel pch (f mod) (f bth) (f aft) (f bnd)
where eval' name = evalDef $ name ++ show n
f x = if null x then Nothing else Just x
defined :: Batch -> Bool
defined Batch { btDur = d, btVel = v, btPch = p } = all (not . null) [d,v,p]
slice :: Int -> Batch -> Batch
slice t batch@Batch { btDur = dur } = take (f 0 0 dur) <!> batch
where f !i _ [] = i
f !i !a (x:xs) = if x + a >= t then succ i else f (succ i) (x + a) xs
toTrack :: Batch -> Int -> Track
toTrack (Batch d v p m t a b) i =
concat (zipWith7 f d v p (r m) (r t) (r a) (r b)) ++ [(0, M.TrackEnd)]
where r = maybe (repeat Nothing) (Just <$>)
f d' v' p' m' t' a' b' =
mixEvents
[ figure m' d' i modP
, figure t' d' i bthP
, figure a' d' i aftP
, figure b' d' i bndP
, [(0, M.NoteOn i p' v'), (d', M.NoteOn i p' 0)] ]
mixEvents :: [Track] -> Track
mixEvents = foldl' mixPair mempty
mixPair :: Track -> Track -> Track
mixPair [] xs = xs
mixPair xs [] = xs
mixPair (x:xs) (y:ys) = r : mixPair xs' ys'
where (r, xs', ys')
| fst x <= fst y = (x, xs, f y (fst x) : ys)
| otherwise = (y, f x (fst y) : xs, ys)
f (i, msg) c = (i c, msg)
figure :: Maybe Int -> Int -> Int -> ModParams -> Track
figure Nothing _ _ _ = []
figure (Just raw) d ch p =
fig p { mpValue = v
, mpFigure = f
, mpDuration = d
, mpChannel = ch }
where (f, v) = quotRem raw 128
fig :: ModParams -> Track
fig ModParams { mpDuration = 0 } = []
fig (ModParams v f d ch p ub db) =
maybe [] (zip (0 : repeat 1) . fmap (p ch)) (gen <*> return v)
where gen = listToMaybe $ drop f
[ figStc ub
, figRtn ub d
, figRtn db d
, figLin ub d
, figLin db d ]
figStc :: (Int, Int) -> Int -> [Int]
figStc be x = [draw be x 1]
figRtn :: (Int, Int) -> Int -> Int -> [Int]
figRtn be q x = f <$> [0..l] ++ reverse [0..(q l 1)]
where f c = draw be (x * c) l
l = q `div` 2
figLin :: (Int, Int) -> Int -> Int -> [Int]
figLin be q x = f <$> [0..q]
where f c = draw be (x * c) q
draw :: (Int, Int) -> Int -> Int -> Int
draw (b, e) n d = b + (n * (e b)) `gdiv` (127 * d)
where x `gdiv` y = round (fromIntegral x / fromIntegral y :: Double)
topDefs :: [String]
topDefs = [x ++ show n |
x <- [defDur,defVel,defPch,defMod,defBth,defAft,defBnd],
n <- [0..mvIndex]]
mvIndex :: Int
mvIndex = 15
defDur :: String
defDur = "dur"
defVel :: String
defVel = "vel"
defPch :: String
defPch = "pch"
defMod :: String
defMod = "mod"
defBth :: String
defBth = "bth"
defAft :: String
defAft = "aft"
defBnd :: String
defBnd = "bnd"