Ticket #3632: tmp.hs

File tmp.hs, 9.3 KB (added by eflister, 4 years ago)
Line 
1{-# LANGUAGE RankNTypes, ExistentialQuantification, RecordWildCards, ScopedTypeVariables, MultiParamTypeClasses, FunctionalDependencies, FlexibleInstances, GeneralizedNewtypeDeriving, FlexibleContexts #-}
2
3module Main () where
4
5import Control.Arrow
6import Control.Applicative
7import System.Random
8import Control.Monad.State
9import Control.Monad.Identity
10import Data.Maybe
11import Data.List
12
13-- *params
14
15tempo     = 200 -- bpm
16timeSig   = TimeSig { numBeats = 4
17                    , unit     = Quarter
18                    }
19
20intervals = [2,1,2,2,1,2,2]; -- minor scale
21
22numMeasures = 8
23noteRange = (-20,20)
24
25-- *main algorithm
26
27composeMeasure :: (MonadSupply Int m, Integral a) => a -> m [Note]
28composeMeasure mn =
29    mapM replaceNoteNum notes
30    where measureNum = fromIntegral mn
31          notes = if measureNum == numMeasures
32                     then [downbeat]
33                     else [badjust n b | n <- [downbeat, adjust downbeat 100 (2/3) (Triplet Eighth)]
34                                       , b <- [0 .. numBeats timeSig - 1]
35                                       ] 
36                                       -- need dev snapshot of ghc (6.12) to update existential records (compare http://www.haskell.org/ghc/docs/latest/html/users_guide/data-type-extensions.html#existential-records
37                                                                                                    -- vs. http://www.haskell.org/ghc/dist/current/docs/html/users_guide/data-type-extensions.html#existential-records)
38{-                                    [n {beat = b} | n <- [downbeat, downbeat {vel = 100, subdiv = 2/3, dur = Triplet Eighth}]
39                                                    , b <- [0 .. numBeats timeSig - 1]
40                                                    ] -}
41          downbeat = Note { midiNum = 60
42                          , vel     = 75
43                          , chan    = 0
44                          , measure = fromIntegral measureNum
45                          , beat    = 0
46                          , subdiv  = 0
47                          , dur     = Triplet Quarter
48                          }
49          replaceNoteNum x = do
50            (Just o) <- untilM ((/= 0) . fromJust) (const $ (fmap signum) <$> snext) $ Just 0
51            (Just m) <- snext
52            let nr :: (Real r) => (Note -> r) -> (Note -> Rational)
53                nr = (toRational .)
54                interval = getInterval (if measureNum == numMeasures
55                                           then 0 
56                                           else m + if odd (mod m $ length intervals) && (and . map (== 0) $ [nr beat, nr subdiv] <*> [x]) -- see discussion of why not to use existentials here http://tunes.org/~nef//logs/haskell/09.08.12
57                                                       then o -- on first beat of each measure play a chord tone
58                                                       else 0
59                                       ) intervals
60            return $ move x interval {- x {midiNum = midiNum x + interval} -} -- see above re: updating existential records
61
62-- *support to eliminate
63
64move :: Note -> Int -> Note
65move Note{..} i = Note {
66      midiNum = midiNum + i
67    , vel     = vel
68    , chan    = chan
69    , measure = measure
70    , beat    = beat
71    , subdiv  = subdiv
72    , dur     = dur
73    }
74
75adjust :: (RealFrac a, NoteDur b) => Note -> Int -> a -> b -> Note
76adjust Note{..} v s d = Note {
77      midiNum = midiNum
78    , vel     = v
79    , chan    = chan
80    , measure = measure
81    , beat    = beat
82    , subdiv  = realToFrac s
83    , dur     = d
84    }
85
86badjust :: Note -> Int -> Note
87badjust Note{..} b = Note {
88      midiNum = midiNum
89    , vel     = vel
90    , chan    = chan
91    , measure = measure
92    , beat    = b
93    , subdiv  = subdiv
94    , dur     = dur
95    }
96
97-- *midi/music util
98
99data DurBase = Whole | Half | Quarter | Eighth | Sixteenth | ThirtySecond deriving (Enum, Bounded, Show, Eq)
100data ModDur = forall x. NoteDur x => Dotted x | Triplet DurBase
101
102data TimeSig = TimeSig {
103      numBeats :: Int
104    , unit     :: DurBase
105    }
106
107data Note = forall x . NoteDur x => Note {
108      midiNum :: Int -- 0-255
109    , vel     :: Int -- 0-255
110    , chan    :: Int -- 0-15
111    , measure :: Integral a => a
112    , beat    :: Int
113    , subdiv  :: RealFrac a => a -- % of beat
114    , dur     :: x
115    }
116
117class NoteDur a where
118    quarters :: (Real x, Floating x) => a -> x
119     
120    calcDurMS :: (Real x, Floating x) => a -> x
121    calcDurMS d = 1000 * 60 * (beats d) / (realToFrac tempo)
122
123    beats :: (Real x, Floating x) => a -> x   
124--  beats d = (quarters d) / (quarters $ unit timeSig) -- want to factor out the application of quarters
125--  beats d = uncurry (/) $ join (***) quarters (d, unit timeSig) -- join (***) from Saizan_ @ #haskell, but isn't existentially polymorphic
126    beats d = uncurry (/) $ both quarters (d, unit timeSig)
127        where both (f :: forall a b. (NoteDur a, Real b, Floating b) => a -> b) (x, y) = (f x, f y) -- lame that this has to be class specific (copumpkin @ #haskell says a 'forall classes' would be nice)
128
129instance NoteDur DurBase where
130    quarters x = z where Just z = lookup x $ zip [minBound .. maxBound] $ map (2 **) $ map realToFrac [2, 1 ..]
131
132instance NoteDur ModDur where
133{- why isn't something like this OK?  scree @ #haskell points out that if NoteDur were a type instead of a class it would work, but then we have to carry around another constructor (ie: NoteDur Dotted Eighth)
134    quarters (x y) = quarters y * case x of
135        Dotted  -> 3 / 2
136        Triplet -> 2 / 3
137-}
138    quarters (Dotted  x) = quarters x * 3 / 2
139    quarters (Triplet x) = quarters x * 2 / 3
140
141instance NoteDur Note where
142     quarters Note{..} = quarters dur
143
144calcStartMS :: RealFrac a => Note -> a
145calcStartMS n = realToFrac $ ((subdiv n) + (fromIntegral ((measure n) * (numBeats timeSig) + (beat n)))) * (calcDurMS $ unit timeSig)
146
147getInterval :: Int -> [Int] -> Int
148getInterval n is
149        | n >= 0    = d * (last s) + if m == 0 
150                                        then 0 
151                                        else s !! (m-1)
152        | otherwise = getInterval (-n) . map (* (-1)) $ reverse is -- could avoid this reverse with some fold fu
153    where (d,m) = divMod n $ length is
154          s     = cumsum is
155
156-- *algorithm support
157
158type SupT = SupplyT Int
159
160compose :: IO ()
161compose = do
162    join . runRandomRIOT noteRange $ mapM_ makeNotes [0 .. numMeasures]
163    putStrLn "done making notes"
164
165makeNotes :: (Integral a) => a -> (SupT IO) ()
166makeNotes thisMeasureNum = do
167    liftIO . putStrLn $ "composing measure " ++ show thisMeasureNum
168    liftIO . mapM_ rptNote =<< composeMeasure thisMeasureNum
169
170rptNote :: Note -> IO ()
171rptNote n = do
172    let startT     = round $ calcStartMS n
173        endT       = startT + (round $ calcDurMS n)
174    putStrLn $ "\t" ++ show startT ++ " - " ++ show endT ++ ": " ++ (show $ midiNum n)
175
176-- *util: random supply
177
178-- adapted from http://www.haskell.org/haskellwiki/New_monads/MonadSupply
179-- hides implementation, handles finite supplies, includes function to get multiple items
180-- much taken from RWH's Supply monad in ch. 15: http://book.realworldhaskell.org/read/programming-with-monads.html#id646649
181-- transformer in ch. 18: http://book.realworldhaskell.org/read/monad-transformers.html#monadtrans.maybet
182
183newtype SupplyT s m a = ST (StateT [s] m a) deriving (Functor, Monad, MonadTrans, MonadIO)
184newtype Supply s a = S (SupplyT s Identity a) deriving (Monad, Functor, MonadSupply s)
185
186class (Monad m, Functor m) => MonadSupply s m | m -> s where
187    snext  :: m (Maybe s)
188    snext  =  head <$> snexts 1
189    snexts :: Integral a => a -> m [Maybe s]
190
191instance (Monad m, Functor m) => MonadSupply s (SupplyT s m) where
192    snexts n = ST $ do -- blackh @ #haskell's solution, cleaner than my Kleislis
193        (these,rest) <- genericSplitAt n <$> get
194        put rest
195        return . genericTake n $ map Just these ++ repeat Nothing
196
197runSupply :: Supply s a -> [s] -> (a, [s])
198runSupply (S m) = runIdentity . runSupplyT m
199
200runSupplyT :: Functor m => SupplyT s m a -> [s] -> m (a, [s])
201runSupplyT (ST s) = runStateT s
202
203evalSupply :: Supply s a -> [s] -> a
204evalSupply (S s) = runIdentity . evalSupplyT s
205
206evalSupplyT :: (Functor m) => SupplyT s m a -> [s] -> m a
207evalSupplyT = fmap fst `dot` runSupplyT
208
209randomsRIO :: Random a => (a,a) -> IO [a] -- note System.Random is known to be very slow, but this application isn't very demanding (see discussion here, incl pointers to nonuniform distributions: http://www.serpentine.com/blog/2009/09/19/a-new-pseudo-random-number-generator-for-haskell/)
210randomsRIO lims = getStdRandom $ first (randomRs lims) . split
211
212runRandomRIO :: (Random s) => (s, s) -> Supply s a -> IO a
213runRandomRIO r s = fmap (evalSupply s) $ randomsRIO r
214
215runRandomRIOT :: (Functor m, Random s) => (s, s) -> SupplyT s m a -> IO (m a)
216runRandomRIOT r s = fmap (evalSupplyT s) $ randomsRIO r
217
218-- *other util
219
220cumsum :: Num a => [a] -> [a]
221cumsum = scanl1 (+)
222
223-- from http://www.haskell.org/haskellwiki/Pointfree#Dot
224-- note (f `dot` g) x = f . g x (http://stackoverflow.com/questions/907306/confusion-about-currying-and-point-free-style-in-haskell)
225dot :: (c -> d) -> (a -> b -> c) -> a -> b -> d
226dot = (.) . (.)
227
228-- from http://www.haskell.org/pipermail/beginners/2009-January/000690.html (via byorgey @ #haskell)
229untilM :: (Monad m) => (a -> Bool) -> (a -> m a) -> a -> m a
230untilM p f x | p x       = return x
231             | otherwise = f x >>= untilM p f
232
233-- *main
234
235main :: IO ()
236main = compose