module CycleAnimation where import Control.Concurrent import Control.Monad.Reader import Control.Monad.State import Data.Bits import Data.Colour.SRGB import GHC.Int (Int16) import Graphics.UI.SDL import Graphics.UI.SDL.TTF.Management import Graphics.UI.SDL.TTF.Render import Graphics.UI.SDL.TTF.Types import Sound.Tidal.Context hiding (Event) import Sound.Tidal.Tempo import Sound.Tidal.Utils import qualified GHC.Word import qualified Graphics.UI.SDL.Framerate as FR import qualified Graphics.UI.SDL.Primitives as SDLP import qualified Graphics.UI.SDL.TTF.General as TTFG import qualified Sound.OSC.FD as FD import qualified Sound.Tidal.Pattern as Pat import Common data Scene = Scene { mouseXY :: (Float, Float) , cursor :: (Float, Float) } data AppConfig = AppConfig { acScreen :: Surface , acFont :: Font , acTempo :: MVar Tempo , acFps :: FR.FPSManager , acPattern :: MVar Pat.ControlPattern } type AppState = StateT Scene IO type AppEnv = ReaderT AppConfig AppState run' :: MVar ControlPattern -> IO () run' mp = withInit [InitEverything] $ do result <- TTFG.init if not result then putStrLn "Failed to init ttf" else do enableUnicode True env <- initEnv mp --ws <- wordMenu (font env) things let scene = Scene (0,0) (0.5,0.5) runLoop env scene runLoop :: AppConfig -> Scene -> IO () runLoop = evalStateT . runReaderT looping -- | Animate pattern looply. -- | Choose form of pattern within 'loop'. looping :: AppEnv () looping = do quit' <- whileEvents action screen <- acScreen `liftM` ask tempoM <- acTempo `liftM` ask fps <- acFps `liftM` ask mp <- acPattern `liftM` ask liftIO $ do pat <- readMVar mp appendFile "pat" $ show pat ++ "\n\n" tempo <- readMVar tempoM beat <- beatNow tempo bgColor <- (mapRGB . surfaceGetPixelFormat) screen 0x00 0x00 0x00 clipRect <- Just `liftM` getClipRect screen void $ fillRect screen clipRect bgColor -- | Use one of -- -- | (1) Cicle form of moving patterns -- drawPatC (100, fi screenHeight / 2) (dirtToColour pat) screen beat -- | (2) Rectangular form of moving patterns drawPatR (0, fi screenHeight) (dirtToColour pat) screen beat Graphics.UI.SDL.flip screen FR.delay fps unless quit' looping where action e = do scene <- get scene' <- handleEvent scene e put scene' initEnv :: MVar ControlPattern -> IO AppConfig initEnv mp = do time' <- FD.time screen <- setVideoMode screenWidth screenHeight screenBpp [SWSurface] font' <- openFont "futura.ttf" 22 setCaption "Cycle" [] tempoMV' <- remoteLocal defaultConfig time' fps <- FR.new FR.init fps return $ AppConfig screen font' tempoMV' fps mp -- Draw one cycle pattern. drawArc :: Surface -> ColourD -> (Double, Double) -- Middle`s coord -> (Double, Double) -- Torus`s internal and external radiuses. -> Double -- (pi*2) * fromRational (s - (toRational $ beat / 8)) -> Double -- ((pi*2) * fromRational (e-s)) -> Double -- pace -> IO () drawArc screen c (x,y) (r,r') t o pace | o <= 0 = return () | otherwise = do let pix = colourToPixel c void $ SDLP.filledPolygon screen coords pix drawArc screen c (x,y) (r,r') t (o - pace) pace return () where a = max t (t + o - pace) -- start width b = t + o -- end width coords :: [(Int16, Int16)] coords = map (\(x',y') -> (floor $ x + x', floor $ y + y')) [ (r * cos a, r * sin a) -- 1 , (r' * cos a, r' * sin a) -- 2 , (r' * cos b, r' * sin b) -- 3 , (r * cos b, r * sin b) -- 4 ] -- Draw cycle patterns continiously. drawPatC :: (Double, Double) -> Pat.Pattern ColourD -> Surface -> Double -> IO () drawPatC (r,r') pat screen beat = mapM_ drawEvents $ event (pos beat) pat where drawEvents :: ((Rational, Rational), [ColourD]) -> IO () drawEvents ((b,e), cs) = mapM_ (\(index', color) -> drawEvent (b,e) color index' (length cs)) (enumerate $ reverse cs) drawEvent :: (Rational, Rational) -> ColourD -> Int -> Int -> IO () drawEvent (b, e) color index' len = do let thickness = (1 / fromIntegral len) * (r' - r) let thickIndex = r + thickness * fromIntegral index' drawArc screen color middle (thickIndex, thickIndex + thickness) ((pi*2) * fromRational (b - pos beat)) ((pi*2) * fromRational (e - b)) (pi/16) -- Draw one rectangle pattern drawRect :: Surface -> ColourD -> (Double, Double) -- thickIndex, thickIndex + thickness -> Double -- ((pi*2) * fromRational (start - pos)) -> Double -- ((pi*2) * fromRational (end - start)) -> Double -- pace (pi/16) -> IO () drawRect screen c (thickStart,thickEnd) t o pace | o <= 0 = return () | otherwise = do let pix = colourToPixel c void $ SDLP.filledPolygon screen coords pix drawRect screen c (thickStart, thickEnd) t (o - pace) pace return () where a = max t (t + o - pace) -- b = t + o coords = map (\(x',y') -> (floor x', floor y')) [ (b, thickStart) -- 1 , (b, thickEnd) -- 2 , (a, thickEnd) -- 3 , (a, thickStart) -- 4 ] -- Draw rectangle patterns continiously drawPatR :: (Double, Double) -> Pat.Pattern ColourD -> Surface -> Double -> IO () drawPatR (x1,x2) p screen beat = mapM_ drawEvents $ event (pos beat) p where drawEvents :: ((Rational, Rational), [ColourD]) -> IO () drawEvents ((b, e), cs) = mapM_ (\(index', c) -> drawEvent (b, e) c index' (length cs)) (enumerate $ reverse cs) drawEvent :: (Rational, Rational) -> ColourD -> Int -> Int -> IO () drawEvent (b, e) color index' len = do let thickness = (1 / fromIntegral len) * (x2 - x1) let thickIndex = thickness * fromIntegral index' let width = fi screenWidth drawRect screen color (thickIndex, thickIndex + thickness) (width * fromRational (b - pos beat)) (width * fromRational (e - b)) 1 event :: Rational -> Pat.Pattern ColourD -> [((Rational, Rational), [ColourD])] event position pat = map (\(Pat.Event _ Arc{..} events) -> ((max start position, min stop (position + 1)), events)) $ queryArc (segmentator pat) (Arc position (position + 1)) whileEvents :: MonadIO m => (Event -> m ()) -> m Bool whileEvents action = do ev <- liftIO pollEvent case ev of Quit -> return True NoEvent -> return False _ -> do action ev whileEvents action textSize :: String -> Font -> IO (Float,Float) textSize text font' = do message <- renderTextSolid font' text (Color 0 0 0) return (fromScreen (surfaceGetWidth message, surfaceGetHeight message)) colourToPixel :: Colour Double -> Pixel colourToPixel c = rgbColor (floor $ r*255) (floor $ g*255) (floor $ b *255) where (RGB r g b) = toSRGB c colourToPixelS :: Surface -> Colour Double -> IO Pixel colourToPixelS surface c = (mapRGB . surfaceGetPixelFormat) surface (floor $ r*255) (floor $ g*255) (floor $ b*255) where (RGB r g b) = toSRGB c rgbColor :: GHC.Word.Word8 -> GHC.Word.Word8 -> GHC.Word.Word8 -> Pixel rgbColor r g b = Pixel ( shiftL (fi r) 24 .|. shiftL (fi g) 16 .|. shiftL (fi b) 8 .|. fi (255 :: Integer) ) pixel :: Surface -> (GHC.Word.Word8,GHC.Word.Word8,GHC.Word.Word8) -> IO Pixel pixel face (r,g,b) = mapRGB (surfaceGetPixelFormat face) r g b screenWidth :: Int screenWidth = 500 screenHeight :: Int screenHeight = 400 screenBpp :: Int screenBpp = 32 -- A middle of window. middle :: (Double, Double) middle = (fromIntegral $ screenWidth `div` 2, fromIntegral $ screenHeight `div` 2) fromScreen :: (Int, Int) -> (Float, Float) fromScreen (x, y) = ( fromIntegral x / fromIntegral screenWidth , fromIntegral y / fromIntegral screenHeight ) pos :: Double -> Rational pos beat = toRational $ beat / 8 isInside :: Integral a => Rect -> a -> a -> Bool isInside (Rect rx ry rw rh) x y = (x' > rx) && (x' < rx + rw) && (y' > ry) && (y' < ry + rh) where (x', y') = (fromIntegral x, fromIntegral y) ctrlDown :: [Modifier] -> Bool ctrlDown = any (`elem` [KeyModLeftCtrl, KeyModRightCtrl]) shiftDown :: [Modifier] -> Bool shiftDown = any (`elem` [ KeyModLeftShift, KeyModRightShift, KeyModShift]) handleEvent :: Scene -> Event -> AppEnv Scene handleEvent scene (KeyDown k) = handleKey scene (symKey k) (symUnicode k) (symModifiers k) handleEvent scene _ = return scene handleKey :: Scene -> SDLKey -> Char -> [Modifier] -> AppEnv Scene handleKey scene SDLK_SPACE _ _ = return scene handleKey scene _ _ _ = return scene applySurface :: Int -> Int -> Surface -> Surface -> Maybe Rect -> IO Bool applySurface x y src dst clip = blitSurface src clip dst rect where rect = Just Rect { rectX = x, rectY = y, rectW = 0, rectH = 0 }