Add realtime animation

master
willbasky 2019-05-10 00:09:36 +05:00
parent c350b1ff19
commit daae6f3ad4
12 changed files with 444 additions and 185 deletions

View File

@ -1,14 +1,54 @@
# tidal-vis # tidal-vis
Tidal is a domain specific language for live coding pattern. This package allows colour patterns to be rendered as PDF or SVG files. See _Examples.hs_ module for more help. Tidal is a domain specific language for live coding pattern. This package allows several things:
## Example 1. OSC messages sent to SC to be dynamicly rendered in realtime with at separate window.
[Demo of realtime visualisation.](https://youtu.be/bZS6WufE8FY)
2. Colour patterns to be rendered as PDF or SVG files. See _Examples.hs_ module for more help.
3. Colour patterns to be rendered to be rendered dynamicly in separate window. See _CycleAnimation.hs_ for more. [Demo.](https://youtu.be/cCmCSSb4vHs)
## (1) Realtime animation during livecoding
1. Add following lines to _BootTidal.hs_
-- OSCTarget for pattern visualizing.
patternTarget = OSCTarget { oName = "Pattern handler", oAddress = "127.0.0.1", oPort = 5050, oPath = "/trigger/something", oShape = Nothing, oLatency = 0.02, oPreamble = [], oTimestamp = BundleStamp }
-- OSCTarget for play music via SuperCollider.
musicTarget = superdirtTarget { oLatency = 0.1, oAddress = "127.0.0.1", oPort = 57120 }
config = defaultConfig {cFrameTimespan = 1/20}
-- Send pattern as osc both to SC and to tidal-vis
tidal <- startMulti [musicTarget, patternTarget] config
-- Send pattern as osc to SC only
-- tidal <- startTidal musicTarget config
2. Comment `tidal <- startTidal...` and uncomment `tidal <- startMulti...`
3. Build _tidal-vis_ and run
cd /tidal-vis
stack build
stack exec tidal-vis
4. Eval your tidal code.
5. Profit.
## (2) Render SVG or PDF
For exanple, when pattern is
density 16 $ every 2 rev $ every 3 (superimpose (iter 4)) $ rev "[black blue darkblue, grey lightblue]" density 16 $ every 2 rev $ every 3 (superimpose (iter 4)) $ rev "[black blue darkblue, grey lightblue]"
Output image is
![0](https://i.imgur.com/MPbpH0n.jpg) ![0](https://i.imgur.com/MPbpH0n.jpg)
To run pattern [animation](https://youtu.be/cCmCSSb4vHs) (not good performance): ## (3) Animate one pattern
To animate pattern (not good performance):
cd ./tidal-vis/ cd ./tidal-vis/
stack repl ./src/CycleAnimation.hs stack repl ./src/CycleAnimation.hs
@ -16,7 +56,7 @@ To run pattern [animation](https://youtu.be/cCmCSSb4vHs) (not good performance):
ah <- run ah <- run
swapMVar ah $ degradeBy 0.3 $ every 3 (fast 3) $ Params.s "[red, white, [purple orange green]]" swapMVar ah $ degradeBy 0.3 $ every 3 (fast 3) $ Params.s "[red, white, [purple orange green]]"
Look at _CycleAnimation.hs_ for more information. Look at _CycleAnimation.hs_ for more information. Look at `looping` function to change animation form.
## Tutorial ## Tutorial

6
app/Main.hs Normal file
View File

@ -0,0 +1,6 @@
module Main where
import Realtime.Server (animeCollectorServerU)
main :: IO ()
main = animeCollectorServerU

BIN
examples/cycle.pdf Normal file

Binary file not shown.

BIN
examples/gradientRect.pdf Normal file

Binary file not shown.

View File

@ -1,6 +1,5 @@
{-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE NamedFieldPuns #-}
module Common module Common
( arrangeEvents ( arrangeEvents
, beatNow , beatNow
@ -9,10 +8,10 @@ module Common
, levels , levels
, remoteLocal , remoteLocal
, segmentator , segmentator
, toPattern
) where ) where
import Control.Concurrent.MVar import Control.Concurrent.MVar
import Data.Bits (shiftR, (.&.)) import Data.Bits (shiftR, (.&.))
import Data.Colour.SRGB (sRGB) import Data.Colour.SRGB (sRGB)
import Data.Function (on) import Data.Function (on)
@ -21,20 +20,18 @@ import Data.List (groupBy, nub, sortOn)
import Data.Maybe (isJust) import Data.Maybe (isJust)
import Data.Time (diffUTCTime, getCurrentTime) import Data.Time (diffUTCTime, getCurrentTime)
import Network.Socket (SockAddr (..), addrAddress, getAddrInfo) import Network.Socket (SockAddr (..), addrAddress, getAddrInfo)
import Sound.Tidal.Context import Sound.Tidal.Context
import qualified Sound.OSC.FD as OSC import qualified Sound.OSC.FD as OSC
import qualified Sound.Tidal.Tempo as Tempo import qualified Sound.Tidal.Tempo as Tempo
-- | Common used functions. -- | Common functions.
fi :: (Integral a, Num b) => a -> b fi :: (Integral a, Num b) => a -> b
fi = fromIntegral fi = fromIntegral
arrangeEvents :: [Event b] -> [[Event b]] arrangeEvents :: [Event b] -> [[Event b]]
arrangeEvents [] = [] arrangeEvents = foldr addEvent []
arrangeEvents (e:es) = addEvent e (arrangeEvents es)
fits :: Event b -> [Event b] -> Bool fits :: Event b -> [Event b] -> Bool
fits (Event _ part' _) events = not $ any (\Event{..} -> isJust $ subArc part' part) events fits (Event _ part' _) events = not $ any (\Event{..} -> isJust $ subArc part' part) events
@ -51,7 +48,7 @@ levels pat = arrangeEvents $ sortOn' ((\Arc{..} -> stop - start) . part) (queryA
sortOn' :: Ord a => (b -> a) -> [b] -> [b] sortOn' :: Ord a => (b -> a) -> [b] -> [b]
sortOn' f = map snd . sortOn fst . map (\x -> let y = f x in y `seq` (y, x)) sortOn' f = map snd . sortOn fst . map (\x -> let y = f x in y `seq` (y, x))
-- | Recover depricated functions for 1.0.7 -- | Recover depricated functions for 1.0.13
dirtToColour :: ControlPattern -> Pattern ColourD dirtToColour :: ControlPattern -> Pattern ColourD
dirtToColour = fmap (stringToColour . show) dirtToColour = fmap (stringToColour . show)
@ -77,7 +74,7 @@ split :: Time -> [Event a] -> [Event a]
split _ [] = [] split _ [] = []
split t (ev@(Event whole Arc{..} value):es) split t (ev@(Event whole Arc{..} value):es)
| t > start && t < stop = | t > start && t < stop =
Event whole (Arc start t) value : Event whole (Arc t stop) value : (split t es) Event whole (Arc start t) value : Event whole (Arc t stop) value : split t es
| otherwise = ev:split t es | otherwise = ev:split t es
points :: [Event a] -> [Time] points :: [Event a] -> [Time]
@ -105,12 +102,15 @@ remoteLocal :: Config -> OSC.Time -> IO (MVar Tempo.Tempo)
remoteLocal config time = do remoteLocal config time = do
let tempoClientPort = cTempoClientPort config let tempoClientPort = cTempoClientPort config
hostname = cTempoAddr config hostname = cTempoAddr config
port = cTempoPort config remotePort = cTempoPort config
(remote_addr:_) <- getAddrInfo Nothing (Just hostname) Nothing (remote_addr:_) <- getAddrInfo Nothing (Just hostname) Nothing
local <- OSC.udpServer "127.0.0.1" tempoClientPort local <- OSC.udpServer "127.0.0.1" tempoClientPort
let (SockAddrInet _ a) = addrAddress remote_addr case addrAddress remote_addr of
remote = SockAddrInet (fromIntegral port) a SockAddrInet _ a -> do
newMVar $ Tempo.defaultTempo time local remote let remote = SockAddrInet (fromIntegral remotePort) a
newMVar $ Tempo.defaultTempo time local remote
_ -> error "wrong Socket"
toPattern :: [Event ControlMap] -> ControlPattern
toPattern evs = Pattern Digital $ const evs

View File

@ -4,165 +4,97 @@ module CycleAnimation where
import Control.Concurrent import Control.Concurrent
import Control.Monad.Reader import Control.Monad.Reader
import Control.Monad.State import Control.Monad.State
import Data.Bits import Data.Bits
import Data.Colour.SRGB import Data.Colour.SRGB
import GHC.Int (Int16) import GHC.Int (Int16)
import Graphics.UI.SDL import Graphics.UI.SDL
import Graphics.UI.SDL.TTF.Management import Graphics.UI.SDL.TTF.Management
import Graphics.UI.SDL.TTF.Render import Graphics.UI.SDL.TTF.Render
import Graphics.UI.SDL.TTF.Types 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 GHC.Word
import qualified Graphics.UI.SDL.Framerate as FR import qualified Graphics.UI.SDL.Framerate as FR
import qualified Graphics.UI.SDL.Primitives as SDLP import qualified Graphics.UI.SDL.Primitives as SDLP
import qualified Graphics.UI.SDL.TTF.General as TTFG import qualified Graphics.UI.SDL.TTF.General as TTFG
import qualified Sound.OSC.FD as FD
import Sound.OSC.FD (time)
import Sound.Tidal.Config
import Sound.Tidal.Core
import Sound.Tidal.ParseBP
import Sound.Tidal.Pattern hiding (Event)
import Sound.Tidal.Tempo
import Sound.Tidal.Utils
import qualified Sound.Tidal.Pattern as Pat import qualified Sound.Tidal.Pattern as Pat
import Common import Common
-- | To run at CLI to see animation. data Scene = Scene
-- | Cycle animation looks like https://www.youtube.com/watch?v=cCmCSSb4vHs { mouseXY :: (Float, Float)
-- | Rectangle animation looks ... , cursor :: (Float, Float)
-- @ }
-- stack repl
-- :set -XOverloadedStrings data AppConfig = AppConfig
-- ah <- run { acScreen :: Surface
-- swapMVar ah $ degradeBy 0.3 $ every 3 (fast 3) $ Params.s "[red, white, [purple orange green]]" , acFont :: Font
-- @ , acTempo :: MVar Tempo
-- | Look at comment for 'loop' function below. , acFps :: FR.FPSManager
runAnimation :: IO (MVar ControlPattern) , acPattern :: MVar Pat.ControlPattern
runAnimation = do }
mp <- newMVar silence
void $ forkIO $ run' mp type AppState = StateT Scene IO
return mp
type AppEnv = ReaderT AppConfig AppState
run' :: MVar ControlPattern -> IO () run' :: MVar ControlPattern -> IO ()
run' mp = withInit [InitEverything] $ run' mp = withInit [InitEverything] $ do
do result <- TTFG.init result <- TTFG.init
if not result if not result
then putStrLn "Failed to init ttf" then putStrLn "Failed to init ttf"
else do enableUnicode True else do
env <- initEnv mp enableUnicode True
--ws <- wordMenu (font env) things env <- initEnv mp
let scene = Scene (0,0) (0.5,0.5) --ws <- wordMenu (font env) things
--putStrLn $ show scene let scene = Scene (0,0) (0.5,0.5)
runLoop env scene runLoop env scene
runLoop :: AppConfig -> Scene -> IO () runLoop :: AppConfig -> Scene -> IO ()
runLoop = evalStateT . runReaderT loop runLoop = evalStateT . runReaderT looping
-- | Animate pattern looply. Choose form inside 'loop'. -- | Animate pattern looply.
-- | It needs to be optimized. -- | Choose form of pattern within 'loop'.
loop :: AppEnv () looping :: AppEnv ()
loop = do looping = do
quit' <- whileEvents act quit' <- whileEvents action
screen <- acScreen `liftM` ask screen <- acScreen `liftM` ask
tempoM <- acTempo `liftM` ask tempoM <- acTempo `liftM` ask
fps <- acFps `liftM` ask fps <- acFps `liftM` ask
mp <- acPattern `liftM` ask mp <- acPattern `liftM` ask
liftIO $ do liftIO $ do
pat <- readMVar mp pat <- readMVar mp
appendFile "pat" $ show pat ++ "\n\n"
tempo <- readMVar tempoM tempo <- readMVar tempoM
beat <- beatNow tempo beat <- beatNow tempo
bgColor <- (mapRGB . surfaceGetPixelFormat) screen 0x00 0x00 0x00 bgColor <- (mapRGB . surfaceGetPixelFormat) screen 0x00 0x00 0x00
clipRect <- Just `liftM` getClipRect screen clipRect <- Just `liftM` getClipRect screen
void $ fillRect screen clipRect bgColor void $ fillRect screen clipRect bgColor
-- | Use one of -- | Use one of
-- --
-- | (1) Cicle form of moving patterns -- | (1) Cicle form of moving patterns
drawPatC (100, fi screenHeight / 2) (dirtToColour pat) screen beat -- drawPatC (100, fi screenHeight / 2) (dirtToColour pat) screen beat
-- | (2) Rectangular form of moving patterns -- | (2) Rectangular form of moving patterns
-- | drawPatR (0, fi screenHeight) (dirtToColour pat) screen beat drawPatR (0, fi screenHeight) (dirtToColour pat) screen beat
Graphics.UI.SDL.flip screen Graphics.UI.SDL.flip screen
FR.delay fps FR.delay fps
unless quit' loop unless quit' looping
where act e = do scene <- get where
scene' <- handleEvent scene e action e = do
put scene' scene <- get
scene' <- handleEvent scene e
data Scene = Scene put 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
screenWidth :: Int
screenWidth = 1024
screenHeight :: Int
screenHeight = 768
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
)
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 (\x -> elem x
[ 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 off
where off = Just Rect { rectX = x, rectY = y, rectW = 0, rectH = 0 }
initEnv :: MVar ControlPattern -> IO AppConfig initEnv :: MVar ControlPattern -> IO AppConfig
initEnv mp = do initEnv mp = do
time' <- time time' <- FD.time
screen <- setVideoMode screenWidth screenHeight screenBpp [SWSurface] screen <- setVideoMode screenWidth screenHeight screenBpp [SWSurface]
font' <- openFont "futura.ttf" 22 font' <- openFont "futura.ttf" 22
setCaption "Cycle" [] setCaption "Cycle" []
@ -179,17 +111,17 @@ drawArc
-> (Double, Double) -- Torus`s internal and external radiuses. -> (Double, Double) -- Torus`s internal and external radiuses.
-> Double -- (pi*2) * fromRational (s - (toRational $ beat / 8)) -> Double -- (pi*2) * fromRational (s - (toRational $ beat / 8))
-> Double -- ((pi*2) * fromRational (e-s)) -> Double -- ((pi*2) * fromRational (e-s))
-> Double -- step -> Double -- pace
-> IO () -> IO ()
drawArc screen c (x,y) (r,r') t o step' drawArc screen c (x,y) (r,r') t o pace
| o <= 0 = return () | o <= 0 = return ()
| otherwise = do | otherwise = do
let pix = colourToPixel c let pix = colourToPixel c
void $ SDLP.filledPolygon screen coords pix void $ SDLP.filledPolygon screen coords pix
drawArc screen c (x,y) (r,r') t (o-step') step' drawArc screen c (x,y) (r,r') t (o - pace) pace
return () return ()
where where
a = max t (t + o - step') -- start width a = max t (t + o - pace) -- start width
b = t + o -- end width b = t + o -- end width
coords :: [(Int16, Int16)] coords :: [(Int16, Int16)]
coords = map (\(x',y') -> (floor $ x + x', floor $ y + y')) coords = map (\(x',y') -> (floor $ x + x', floor $ y + y'))
@ -206,41 +138,38 @@ drawPatC
-> Surface -> Surface
-> Double -> Double
-> IO () -> IO ()
drawPatC (r,r') pat screen beat = mapM_ drawEvents $ event pos pat drawPatC (r,r') pat screen beat = mapM_ drawEvents $ event (pos beat) pat
where where
pos :: Rational
pos = toRational $ beat / 8
drawEvents :: ((Rational, Rational), [ColourD]) -> IO () drawEvents :: ((Rational, Rational), [ColourD]) -> IO ()
drawEvents ((begin,end), cs) = drawEvents ((b,e), cs) =
mapM_ (\(index', color) -> drawEvent (begin,end) color index' (length cs)) mapM_ (\(index', color) -> drawEvent (b,e) color index' (length cs))
(enumerate $ reverse cs) (enumerate $ reverse cs)
drawEvent :: (Rational, Rational) -> ColourD -> Int -> Int -> IO () drawEvent :: (Rational, Rational) -> ColourD -> Int -> Int -> IO ()
drawEvent (begin, end) color index' len = do drawEvent (b, e) color index' len = do
let thickness = (1 / fromIntegral len) * (r' - r) let thickness = (1 / fromIntegral len) * (r' - r)
let thickIndex = r + thickness * fromIntegral index' let thickIndex = r + thickness * fromIntegral index'
drawArc screen color middle (thickIndex, thickIndex + thickness) drawArc screen color middle (thickIndex, thickIndex + thickness)
((pi*2) * fromRational (begin - pos)) ((pi*2) * fromRational (end - begin)) (pi/16) ((pi*2) * fromRational (b - pos beat)) ((pi*2) * fromRational (e - b)) (pi/16)
-- Draw one cycle patterns -- Draw one rectangle pattern
drawRect :: Surface drawRect :: Surface
-> ColourD -> ColourD
-> (Double, Double) -- thickIndex, thickIndex + thickness -> (Double, Double) -- thickIndex, thickIndex + thickness
-> Double -- ((pi*2) * fromRational (start - pos)) -> Double -- ((pi*2) * fromRational (start - pos))
-> Double -- ((pi*2) * fromRational (end - start)) -> Double -- ((pi*2) * fromRational (end - start))
-> Double -- step (pi/16) -> Double -- pace (pi/16)
-> IO () -> IO ()
drawRect screen c (thickStart,thickEnd) t o step drawRect screen c (thickStart,thickEnd) t o pace
| o <= 0 = return () | o <= 0 = return ()
| otherwise = do | otherwise = do
let pix = colourToPixel c let pix = colourToPixel c
void $ SDLP.filledPolygon screen coords pix void $ SDLP.filledPolygon screen coords pix
drawRect screen c (thickStart, thickEnd) t (o - step) step drawRect screen c (thickStart, thickEnd) t (o - pace) pace
return () return ()
where where
a = max t (t + o - step) -- a = max t (t + o - pace) --
b = t + o b = t + o
coords = map (\(x',y') -> (floor x', floor y')) coords = map (\(x',y') -> (floor x', floor y'))
@ -250,39 +179,36 @@ drawRect screen c (thickStart,thickEnd) t o step
, (a, thickStart) -- 4 , (a, thickStart) -- 4
] ]
-- Draw cycle patterns continiously -- Draw rectangle patterns continiously
drawPatR :: (Double, Double) -> Pat.Pattern ColourD -> Surface -> Double -> IO () drawPatR :: (Double, Double) -> Pat.Pattern ColourD -> Surface -> Double -> IO ()
drawPatR (x1,x2) p screen beat = mapM_ drawEvents $ event pos p drawPatR (x1,x2) p screen beat = mapM_ drawEvents $ event (pos beat) p
where where
pos :: Rational
pos = toRational $ beat / 8
drawEvents :: ((Rational, Rational), [ColourD]) -> IO () drawEvents :: ((Rational, Rational), [ColourD]) -> IO ()
drawEvents ((begin, end), cs) = drawEvents ((b, e), cs) =
mapM_ (\(index', c) -> drawEvent (begin, end) c index' (length cs)) (enumerate $ reverse cs) mapM_ (\(index', c) -> drawEvent (b, e) c index' (length cs)) (enumerate $ reverse cs)
drawEvent :: (Rational, Rational) -> ColourD -> Int -> Int -> IO () drawEvent :: (Rational, Rational) -> ColourD -> Int -> Int -> IO ()
drawEvent (begin, end) color index' len = do drawEvent (b, e) color index' len = do
let thickness = (1 / fromIntegral len) * (x2 - x1) let thickness = (1 / fromIntegral len) * (x2 - x1)
let thickIndex = thickness * fromIntegral index' let thickIndex = thickness * fromIntegral index'
let width = fi screenWidth let width = fi screenWidth
drawRect screen color (thickIndex, thickIndex + thickness) drawRect screen color (thickIndex, thickIndex + thickness)
(width * fromRational (begin - pos)) (width * fromRational (end - begin)) 1 (width * fromRational (b - pos beat)) (width * fromRational (e - b)) 1
event :: Rational -> Pat.Pattern ColourD -> [((Rational, Rational), [ColourD])] event :: Rational -> Pat.Pattern ColourD -> [((Rational, Rational), [ColourD])]
event pos pat = map (\(Pat.Event _ Arc{..} events) -> event position pat = map (\(Pat.Event _ Arc{..} events) ->
((max start pos, min stop (pos + 1)), events)) ((max start position, min stop (position + 1)), events))
$ queryArc (segmentator pat) (Arc pos (pos + 1)) $ queryArc (segmentator pat) (Arc position (position + 1))
whileEvents :: MonadIO m => (Event -> m ()) -> m Bool whileEvents :: MonadIO m => (Event -> m ()) -> m Bool
whileEvents act = do whileEvents action = do
ev <- liftIO pollEvent ev <- liftIO pollEvent
case ev of case ev of
Quit -> return True Quit -> return True
NoEvent -> return False NoEvent -> return False
_ -> do _ -> do
act ev action ev
whileEvents act whileEvents action
textSize :: String -> Font -> IO (Float,Float) textSize :: String -> Font -> IO (Float,Float)
textSize text font' = textSize text font' =
@ -290,14 +216,12 @@ textSize text font' =
return (fromScreen (surfaceGetWidth message, surfaceGetHeight message)) return (fromScreen (surfaceGetWidth message, surfaceGetHeight message))
colourToPixel :: Colour Double -> Pixel colourToPixel :: Colour Double -> Pixel
colourToPixel c = colourToPixel c = rgbColor (floor $ r*255) (floor $ g*255) (floor $ b *255)
-- mapRGB (surfaceGetPixelFormat screen) 255 255 255
rgbColor (floor $ r*255) (floor $ g*255) (floor $ b *255)
where (RGB r g b) = toSRGB c where (RGB r g b) = toSRGB c
colourToPixelS :: Surface -> Colour Double -> IO Pixel colourToPixelS :: Surface -> Colour Double -> IO Pixel
colourToPixelS s c = colourToPixelS surface c =
(mapRGB . surfaceGetPixelFormat) s (floor $ r*255) (floor $ g*255) (floor $ b*255) (mapRGB . surfaceGetPixelFormat) surface (floor $ r*255) (floor $ g*255) (floor $ b*255)
where (RGB r g b) = toSRGB c where (RGB r g b) = toSRGB c
rgbColor :: GHC.Word.Word8 -> GHC.Word.Word8 -> GHC.Word.Word8 -> Pixel rgbColor :: GHC.Word.Word8 -> GHC.Word.Word8 -> GHC.Word.Word8 -> Pixel
@ -311,5 +235,48 @@ rgbColor r g b = Pixel
pixel :: Surface -> (GHC.Word.Word8,GHC.Word.Word8,GHC.Word.Word8) -> IO Pixel pixel :: Surface -> (GHC.Word.Word8,GHC.Word.Word8,GHC.Word.Word8) -> IO Pixel
pixel face (r,g,b) = mapRGB (surfaceGetPixelFormat face) r g b 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 }

View File

@ -9,10 +9,7 @@ import VisCycle
import VisGradient import VisGradient
-- | Examples -- | Examples how to render still images to PDF or SVG formats.
--
-- | For pattern animation look at 'runAnimation' function at 'CycleAnimation.hs' module.
-- | There two forms of moving patterns: cycle and rectangle.
-- --
-- | Here is renders of still images only. -- | Here is renders of still images only.
main :: IO () main :: IO ()
@ -36,6 +33,18 @@ gradientRect = renderGradientPDF "./examples/gradientRect" pip
matCycleWithBorders :: IO () matCycleWithBorders :: IO ()
matCycleWithBorders = renderCyclePDF "./examples/cycle" "background text" pip matCycleWithBorders = renderCyclePDF "./examples/cycle" "background text" pip
repeater :: Pattern ColourD
repeater = dirtToColour
$ juxBy 0.6 brak
$ every 2 ((* speed (1 + sine)) . ply 4)
$ stack
[ s "bd:4 ~ ~ drum:3 ~ ~ drum:2 ~"
, s "~ wind:1/2 hh:9"
, s "subroc3d:9(2,7)"
]
# speed 0.5
# legato 1
-- | Prepared patterns. -- | Prepared patterns.
foo :: Pattern ColourD foo :: Pattern ColourD
foo = dirtToColour $ striate 16 $ sound "[bd*3? dr2, ~ casio ~, [bd arpy]]" # n foo = dirtToColour $ striate 16 $ sound "[bd*3? dr2, ~ casio ~, [bd arpy]]" # n

57
src/Realtime/Animation.hs Normal file
View File

@ -0,0 +1,57 @@
module Realtime.Animation
( movingPatterns
) where
import Control.Concurrent
import Data.Maybe (fromMaybe)
import Data.Sequence (Seq (..), (<|))
import Graphics.Gloss
import Graphics.Gloss.Interface.IO.Simulate
import Realtime.Types (ColorI)
import qualified Data.Sequence as S
window :: Display
window = InWindow "Nice Window" (500, 500) (20, 20)
background :: Color
background = greyN 0.1
movingPatterns :: MVar [ColorI] -> IO ()
movingPatterns tp = simulateIO window background 12
(S.singleton [(200,100,200,250)])
(pure . pictures . seqToPics)
$ \_ _ seqColors -> do
mColors <- tryTakeMVar tp
let colsNew = fromMaybe [] mColors
let headColors = seqColors `S.index` 0
pure $ if headColors==colsNew || null colsNew then seqColors else addColorList colsNew seqColors
where
seqToPics :: Seq [ColorI] -> [Picture]
seqToPics = S.foldMapWithIndex (\i c -> makeLine (length c) i c)
makeLine :: Int -> Int -> [ColorI] -> [Picture]
makeLine cLength i = map (\(n,col) -> rectLinesDown col n cLength i) . zip [0..]
-- Keep circle list length equal to 'n'.
refrain :: Int -> Seq [ColorI] -> Seq [ColorI]
refrain n xs
| S.length xs <= n = xs
| otherwise = S.take n xs
-- Every round number spawn circle and add it to right end. Colorize new circle with new color.
addColorList :: [ColorI] -> Seq [ColorI] -> Seq [ColorI]
addColorList colors seqColors = colors <| refrain 10 seqColors
rectLinesDown :: ColorI -> Float -> Int -> Int -> Picture
rectLinesDown col n l i
= translate (piece * n - 250 + piece / 2) (225 - 50 * fromIntegral i)
$ color (makeColorFromIntTuple col)
$ rectangleSolid piece 50
where
piece = 500 / fromIntegral l
makeColorFromIntTuple :: (Int, Int, Int, Int) -> Color
makeColorFromIntTuple (r,g,b,a) = makeColorI r g b a

58
src/Realtime/Server.hs Normal file
View File

@ -0,0 +1,58 @@
module Realtime.Server
( animeCollectorServerU
) where
import Control.Concurrent
import Control.Concurrent.Async (race_)
import Control.Concurrent.Chan.Unagi.Bounded (InChan, OutChan)
import Control.Monad
import Sound.OSC
import qualified Control.Concurrent.Chan.Unagi.Bounded as U
import qualified Sound.OSC.FD as FD
import Realtime.Animation (movingPatterns)
import Realtime.Types (ColorI, TidalPacket (..), packetToTidalPacket)
-- Command to start the server in a repl for testing
-- do u <- t0; udp_close u; hoscServerTPU
animeCollectorServerU :: IO ()
animeCollectorServerU = do
(inChan, outChan) <- U.newChan 100
mvar <- newEmptyMVar
race_ (hoscServerTPU inChan) $ race_ (collector outChan mvar) (movingPatterns mvar)
t0 :: IO UDP
t0 = udpServer "127.0.0.1" 5050
-- Listen to osc packets and write them to channel.
hoscServerTPU :: InChan TidalPacket -> IO ()
hoscServerTPU inChan = FD.withTransport t0 $ \udp -> forever $ do
packet <- udp_recv_packet udp
let tp = packetToTidalPacket packet
U.writeChan inChan tp
-- Collect sync packets to list and put mvar for animation.
collector :: OutChan TidalPacket -> MVar [ColorI] -> IO ()
collector outChan mvColors = do
buffer <- newEmptyMVar
forever $ do
c <- U.readChan outChan
mtp <- tryTakeMVar buffer
case mtp of
Nothing -> putMVar buffer (tpTime c, [tpColor c])
Just tp ->
if fst tp == tpTime c
then void $ putMVar buffer (toTuple c tp)
else do
putMVar buffer (tpTime c, [tpColor c])
putMVar mvColors $ snd tp
-- Take time and color.
toTuple :: TidalPacket -> (Double, [ColorI]) -> (Double, [ColorI])
toTuple tp (f,tps) = (f, tpColor tp : tps)

84
src/Realtime/Types.hs Normal file
View File

@ -0,0 +1,84 @@
module Realtime.Types
( TidalPacket (..)
, ColorI
, defaultTidalPacket
, packetToTidalPacket
, parsePacket
) where
import Data.Bits (shiftR, (.&.))
import Data.Hashable (hash)
import Data.Maybe (fromMaybe)
import Sound.OSC
data TidalPacket = TidalPacket
{ tpTime :: Double
, tpCycle :: Float
, tpDelta :: Float
, tpColor :: ColorI
} deriving (Eq, Show)
type ColorI = (Int, Int, Int, Int)
defaultTidalPacket :: TidalPacket
defaultTidalPacket = TidalPacket
{ tpTime = immediately
, tpCycle = 1.0
, tpDelta = 1.0
, tpColor = (100, 200, 50, 250)
}
parsePacket :: Packet -> Maybe (Int,Int,Int,Int)
parsePacket p = tupleI list
where
list = mapM datum_integral . messageDatum =<< packet_to_message p
tupleI = \case
Nothing -> Nothing
Just list' -> case list' of
(r:g:b:a:_) -> Just (r,g,b,a)
_ -> Nothing
stringToColour :: String -> (Int,Int,Int,Int)
stringToColour str = (r, g, b, 250)
where
i = hash str `mod` 16777216
r = (i .&. 0xFF0000) `shiftR` 16
g = (i .&. 0x00FF00) `shiftR` 8
b = i .&. 0x0000FF
deleteDatumValue :: String -> [Datum] -> [Datum]
deleteDatumValue d ds = go
where
go = case break (==d') ds of
(f,x:_:xs) -> f ++ (x:xs)
_ -> []
d' = string d
roundFloats :: Datum -> Datum
roundFloats = \case
Float d_float -> Float (fromInteger (round $ d_float * 10000) / 10000)
x -> x
takeDatumValue :: String -> [Datum] -> Datum
takeDatumValue d ds = go
where
go = case break (== d') ds of
(_,_:v:_) -> v
_ -> string "No value for your datum"
d' = string d
packetToTidalPacket :: Packet -> TidalPacket
packetToTidalPacket p = TidalPacket
{ tpTime = bundleTime bund
, tpCycle = cycle'
, tpDelta = delta'
, tpColor = color'
}
where
bund = packet_to_bundle p
datums = concatMap messageDatum $ bundleMessages bund
cycle' = takeFloat "cycle" datums
delta' = takeFloat "delta" datums
color' = stringToColour $ show $ deleteDatumValue "cycle" datums
takeFloat str = fromMaybe 0 . datum_floating . roundFloats . takeDatumValue str

View File

@ -1,14 +1,15 @@
resolver: lts-13.8 resolver: lts-13.19
# packages: [] # packages: []
extra-deps: extra-deps:
- SDL-0.6.7.0
- cairo-0.13.6.0 - cairo-0.13.6.0
- tidal-1.0.7
- gtk2hs-buildtools-0.13.5.0 - gtk2hs-buildtools-0.13.5.0
- SDL-0.6.7.0
- SDL-gfx-0.7.0.0 - SDL-gfx-0.7.0.0
- SDL-image-0.6.2.0 - SDL-image-0.6.2.0
- SDL-ttf-0.6.3.0 - SDL-ttf-0.6.3.0
- unagi-chan-0.4.1.0

View File

@ -1,7 +1,6 @@
name: tidal-vis name: tidal-vis
version: 1.0.7 version: 1.0.13
synopsis: Visual rendering for Tidal patterns synopsis: Visual rendering for Tidal patterns and osc messages
-- description:
homepage: http://yaxu.org/tidal/ homepage: http://yaxu.org/tidal/
license: GPL-3 license: GPL-3
license-file: LICENSE license-file: LICENSE
@ -17,10 +16,34 @@ cabal-version: 2.0
Description: Tidal is a domain specific language for live coding pattern. This package allows colour patterns to be rendered as PDF or SVG files. Description: Tidal is a domain specific language for live coding pattern. This package allows colour patterns to be rendered as PDF or SVG files.
executable tidal-vis
hs-source-dirs: app
main-is: Main.hs
ghc-options: -Wall
-threaded
-rtsopts
-with-rtsopts=-N
-Wincomplete-uni-patterns
-Wincomplete-record-updates
-Wcompat
-Widentities
-Wredundant-constraints
-fhide-source-paths
-Wpartial-fields
build-depends: base
, tidal-vis
default-language: Haskell2010
library library
Exposed-modules: Common Exposed-modules: Common
CycleAnimation CycleAnimation
Examples Examples
Realtime.Animation
Realtime.Server
Realtime.Types
Vis Vis
VisCycle VisCycle
VisGradient VisGradient
@ -28,20 +51,34 @@ library
hs-source-dirs: src hs-source-dirs: src
Build-depends: base < 5 Build-depends: base < 5
, tidal>=1.0.7 , async
, colour
, cairo , cairo
, colour
, containers
, gloss
, hashable
, hosc
, SDL , SDL
, mtl
, SDL-gfx , SDL-gfx
, SDL-image , SDL-image
, SDL-ttf , SDL-ttf
, hosc , mtl
, hashable
, time
, network , network
, tidal>=1.0.13
, time
, unagi-chan
ghc-options: -Wall
-Wincomplete-uni-patterns
-Wincomplete-record-updates
-Wcompat
-Widentities
-Wredundant-constraints
-fhide-source-paths
-Wpartial-fields
default-language: Haskell2010 default-language: Haskell2010
default-extensions: OverloadedStrings default-extensions: OverloadedStrings
RecordWildCards RecordWildCards
LambdaCase