tidal-vis/src/CycleAnimation.hs

283 lines
8.8 KiB
Haskell

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 }