module Mida.Midi
( genMidi
, topDefs )
where
import Control.Monad.State.Strict
import Data.Foldable (foldl')
import Data.List (zipWith7)
import Mida.Language (HasEnv, setRandGen, evalDef)
import Numeric.Natural
import Prelude hiding (mod)
import qualified Codec.Midi as Midi
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 :: !(Maybe Figure)
, mpDuration :: !Int
, mpChannel :: !Int
, mpProducer :: !(Int -> Int -> Midi.Message)
, mpUpBounds :: !(Int, Int)
, mpDnBounds :: !(Int, Int)
}
data Figure
= FigStatic
| FigUpDown
| FigDownUp
| FigUp
| FigDown
deriving (Eq, Show, Bounded, Enum)
modP :: ModParams
modP = ModParams
{ mpValue = 0
, mpFigure = Just FigStatic
, mpDuration = 0
, mpChannel = 0
, mpProducer = flip Midi.ControlChange 1
, mpUpBounds = (0x0000, 0x007f)
, mpDnBounds = (0x007f, 0x0000) }
bthP :: ModParams
bthP = modP
{ mpProducer = flip Midi.ControlChange 2 }
aftP :: ModParams
aftP = modP
{ mpProducer = Midi.ChannelPressure }
bndP :: ModParams
bndP = modP
{ mpProducer = Midi.PitchWheel
, mpUpBounds = (0x2000, 0x3fff)
, mpDnBounds = (0x2000, 0x0000) }
type Track = Midi.Track Int
genMidi :: HasEnv m
=> Natural
-> Natural
-> Natural
-> m Midi.Midi
genMidi seed q b = do
setRandGen seed
voices <- filter defined <$> mapM request [0..mvIndex]
return Midi.Midi
{ Midi.fileType = Midi.MultiTrack
, Midi.timeDiv = Midi.TicksPerBeat (fromIntegral q)
, Midi.tracks = zipWith (toTrack . slice (b * q)) voices [0..] }
request :: HasEnv m
=> Natural
-> 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 = fmap fromIntegral <$> evalDef (name ++ show n)
f x = if null x then Nothing else Just x
defined :: Batch -> Bool
defined Batch {..} = all (not . null) [btDur, btVel, btPch]
slice
:: Natural
-> Batch
-> Batch
slice t' batch@Batch {..} = take (f 0 0 btDur) <!> batch
where t = fromIntegral t'
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, Midi.TrackEnd)]
where r = maybe (repeat Nothing) (fmap 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, Midi.NoteOn i p' v')
, (d', Midi.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 (fi, v) = quotRem raw 128
f | fi <= fromEnum (maxBound :: Figure) = Just (toEnum fi)
| otherwise = Nothing
fig :: ModParams -> Track
fig ModParams { mpDuration = 0 } = []
fig (ModParams v f d ch p ub db) =
let getGen x = case x of
FigStatic -> figStc ub
FigUpDown -> figRtn ub d
FigDownUp -> figRtn db d
FigUp -> figLin ub d
FigDown -> figLin db d
in maybe [] (zip (0 : repeat 1) . fmap (p ch)) (getGen <$> f <*> pure v)
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 :: Natural
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"