moved from tidal repo
parent
d827774658
commit
0aea9d73df
|
|
@ -0,0 +1,283 @@
|
||||||
|
module Sound.Tidal.Cycle where
|
||||||
|
|
||||||
|
import Control.Monad
|
||||||
|
import Control.Monad.State
|
||||||
|
import Control.Monad.Reader
|
||||||
|
import Control.Concurrent.MVar
|
||||||
|
import Data.Array.IArray
|
||||||
|
|
||||||
|
import Graphics.UI.SDL
|
||||||
|
import Graphics.UI.SDL.Image
|
||||||
|
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 Graphics.UI.SDL.TTF.Management
|
||||||
|
import Graphics.UI.SDL.TTF.Render
|
||||||
|
import Graphics.UI.SDL.TTF.Types
|
||||||
|
import Data.Maybe (listToMaybe, fromMaybe, fromJust, isJust, catMaybes)
|
||||||
|
import GHC.Int (Int16)
|
||||||
|
import Data.List (intercalate, tails, nub, sortBy)
|
||||||
|
import Data.Colour
|
||||||
|
import Data.Colour.Names
|
||||||
|
import Data.Colour.SRGB
|
||||||
|
import Data.Colour.RGBSpace.HSV (hsv)
|
||||||
|
import qualified GHC.Word
|
||||||
|
import Data.Bits
|
||||||
|
import Data.Ratio
|
||||||
|
import Debug.Trace (trace)
|
||||||
|
import Data.Fixed (mod')
|
||||||
|
import Control.Concurrent
|
||||||
|
import System.Exit
|
||||||
|
|
||||||
|
import Sound.OSC.FD
|
||||||
|
|
||||||
|
import Sound.Tidal.Stream (ParamPattern)
|
||||||
|
import Sound.Tidal.Pattern
|
||||||
|
import Sound.Tidal.Parse
|
||||||
|
import Sound.Tidal.Tempo
|
||||||
|
import qualified Sound.Tidal.Time as Time
|
||||||
|
import Sound.Tidal.Utils
|
||||||
|
|
||||||
|
|
||||||
|
--enumerate :: [a] -> [(Int, a)]
|
||||||
|
--enumerate = zip [0..]
|
||||||
|
|
||||||
|
maybeHead [] = Nothing
|
||||||
|
maybeHead (x:_) = Just x
|
||||||
|
|
||||||
|
sortByFst :: Ord a => [(a, b)] -> [(a, b)]
|
||||||
|
sortByFst = sortBy (\a b -> compare (fst a) (fst b))
|
||||||
|
|
||||||
|
parenthesise :: String -> String
|
||||||
|
parenthesise x = "(" ++ x ++ ")"
|
||||||
|
|
||||||
|
spaces :: Int -> String
|
||||||
|
spaces n = take n $ repeat ' '
|
||||||
|
|
||||||
|
single :: [a] -> Maybe a
|
||||||
|
single (x:[]) = Just x
|
||||||
|
single _ = Nothing
|
||||||
|
|
||||||
|
fromJust' (Just x) = x
|
||||||
|
fromJust' Nothing = error "nothing is just"
|
||||||
|
|
||||||
|
data Scene = Scene {mouseXY :: (Float, Float),
|
||||||
|
cursor :: (Float, Float)
|
||||||
|
}
|
||||||
|
|
||||||
|
data AppConfig = AppConfig {
|
||||||
|
screen :: Surface,
|
||||||
|
font :: Font,
|
||||||
|
tempoMV :: MVar (Tempo),
|
||||||
|
fr :: FR.FPSManager,
|
||||||
|
mpat :: MVar (Pattern ColourD)
|
||||||
|
}
|
||||||
|
|
||||||
|
type AppState = StateT Scene IO
|
||||||
|
type AppEnv = ReaderT AppConfig AppState
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
screenWidth = 1024
|
||||||
|
screenHeight = 768
|
||||||
|
screenBpp = 32
|
||||||
|
middle = (fromIntegral $ screenWidth`div`2,fromIntegral $ screenHeight`div`2)
|
||||||
|
|
||||||
|
toScreen :: (Float, Float) -> (Int, Int)
|
||||||
|
toScreen (x, y) = (floor (x * (fromIntegral screenWidth)),
|
||||||
|
floor (y * (fromIntegral screenHeight))
|
||||||
|
)
|
||||||
|
|
||||||
|
toScreen16 :: (Float, Float) -> (Int16, Int16)
|
||||||
|
toScreen16 (x, y) = (fromIntegral $ floor (x * (fromIntegral screenWidth)),
|
||||||
|
fromIntegral $ floor (y * (fromIntegral screenHeight))
|
||||||
|
)
|
||||||
|
|
||||||
|
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 mods = or $ map (\x -> elem x [KeyModLeftCtrl,
|
||||||
|
KeyModRightCtrl
|
||||||
|
]
|
||||||
|
) mods
|
||||||
|
|
||||||
|
shiftDown mods = or $ map (\x -> elem x [KeyModLeftShift,
|
||||||
|
KeyModRightShift,
|
||||||
|
KeyModShift
|
||||||
|
]
|
||||||
|
) mods
|
||||||
|
|
||||||
|
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 offset
|
||||||
|
where offset = Just Rect { rectX = x, rectY = y, rectW = 0, rectH = 0 }
|
||||||
|
|
||||||
|
initEnv :: MVar (Pattern ColourD) -> IO AppConfig
|
||||||
|
initEnv mp = do
|
||||||
|
screen <- setVideoMode screenWidth screenHeight screenBpp [SWSurface]
|
||||||
|
font <- openFont "futura.ttf" 22
|
||||||
|
setCaption "Cycle" []
|
||||||
|
tempoMV <- tempoMVar
|
||||||
|
fps <- FR.new
|
||||||
|
FR.init fps
|
||||||
|
FR.set fps 15
|
||||||
|
return $ AppConfig screen font tempoMV fps mp
|
||||||
|
|
||||||
|
blankWidth = 0.015
|
||||||
|
|
||||||
|
drawArc' :: Surface -> ColourD -> (Double, Double) -> (Double, Double) -> Double -> Double -> Double -> IO ()
|
||||||
|
drawArc' screen c (x,y) (r,r') t o step | o <= 0 = return ()
|
||||||
|
| otherwise =
|
||||||
|
do let pix = (colourToPixel c)
|
||||||
|
steps = [t, (t + step) .. (t + o)]
|
||||||
|
coords = map (\s -> (floor $ x + (r*cos(s)),floor $ y + (r*sin(s)))) steps
|
||||||
|
++ map (\s -> (floor $ x + (r'*cos(s)),floor $ y + (r'*sin(s)))) (reverse steps)
|
||||||
|
SDLP.filledPolygon screen coords pix
|
||||||
|
--drawArc screen c (x,y) (r,r') t (o-step) step
|
||||||
|
return ()
|
||||||
|
where a = max t (t + o - step)
|
||||||
|
b = t + o
|
||||||
|
|
||||||
|
|
||||||
|
drawArc :: Surface -> ColourD -> (Double, Double) -> (Double, Double) -> Double -> Double -> Double -> IO ()
|
||||||
|
drawArc screen c (x,y) (r,r') t o step | o <= 0 = return ()
|
||||||
|
| otherwise =
|
||||||
|
do let pix = (colourToPixel c)
|
||||||
|
SDLP.filledPolygon screen coords pix
|
||||||
|
drawArc screen c (x,y) (r,r') t (o-step) step
|
||||||
|
return ()
|
||||||
|
where a = max t (t + o - step)
|
||||||
|
b = t + o
|
||||||
|
coords = map ((\(x',y') -> (floor $ x + x', floor $ y + y')))
|
||||||
|
[(r * cos(a), r * sin(a)),
|
||||||
|
(r' * cos(a), r' * sin(a)),
|
||||||
|
(r' * cos(b), r' * sin(b)),
|
||||||
|
(r * cos(b), r * sin(b))
|
||||||
|
]
|
||||||
|
|
||||||
|
loop :: AppEnv ()
|
||||||
|
loop = do
|
||||||
|
quit <- whileEvents $ act
|
||||||
|
screen <- screen `liftM` ask
|
||||||
|
font <- font `liftM` ask
|
||||||
|
tempoM <- tempoMV `liftM` ask
|
||||||
|
fps <- fr `liftM` ask
|
||||||
|
scene <- get
|
||||||
|
mp <- mpat `liftM` ask
|
||||||
|
liftIO $ do
|
||||||
|
pat <- readMVar mp
|
||||||
|
tempo <- readMVar tempoM
|
||||||
|
beat <- beatNow tempo
|
||||||
|
bgColor <- (mapRGB . surfaceGetPixelFormat) screen 0x00 0x00 0x00
|
||||||
|
clipRect <- Just `liftM` getClipRect screen
|
||||||
|
fillRect screen clipRect bgColor
|
||||||
|
--drawArc screen middle (100,110) ((beat) * pi) (pi/2) (pi/32)
|
||||||
|
drawPat middle (100,(fi screenHeight)/2) pat screen beat
|
||||||
|
Graphics.UI.SDL.flip screen
|
||||||
|
FR.delay fps
|
||||||
|
unless quit loop
|
||||||
|
where act e = do scene <- get
|
||||||
|
scene' <- handleEvent scene e
|
||||||
|
put $ scene'
|
||||||
|
|
||||||
|
drawPat :: (Double, Double) -> (Double, Double) -> Pattern ColourD -> Surface -> Double -> IO ()
|
||||||
|
drawPat (x, y) (r,r') p screen beat = mapM_ drawEvents es
|
||||||
|
where es = map (\(_, (s,e), evs) -> ((max s pos, min e (pos + 1)), evs)) $ arc (segment p) (pos, pos + 1)
|
||||||
|
pos = toRational $ beat / 8
|
||||||
|
drawEvents ((s,e), cs) =
|
||||||
|
mapM_ (\(n', c) -> drawEvent (s,e) c n' (length cs)) (enumerate $ reverse cs)
|
||||||
|
drawEvent (s,e) c n' len =
|
||||||
|
do let thickness = (1 / fromIntegral len) * (r' - r)
|
||||||
|
let start = r + thickness * (fromIntegral n')
|
||||||
|
drawArc screen c middle (start,start+thickness) ((pi*2) * (fromRational (s-pos))) ((pi*2) * fromRational (e-s)) (pi/16)
|
||||||
|
{- (thickLine h (n*scale+n') (linesz/ (fromIntegral scale))
|
||||||
|
(x1 + (xd * fromRational (e-pos)))
|
||||||
|
(y1 + (yd * fromRational (e-pos)))
|
||||||
|
(x1 + (xd * fromRational (s-pos)))
|
||||||
|
(y1 + (yd * fromRational (s-pos)))
|
||||||
|
)
|
||||||
|
screen (colourToPixel c)-}
|
||||||
|
|
||||||
|
segment2 :: Pattern a -> Pattern [(Bool, a)]
|
||||||
|
segment2 p = Pattern $ \(s,e) -> filter (\(_, (s',e'),_) -> s' < e && e' > s) $ groupByTime (segment2' (arc (fmap (\x -> (True, x)) p) (s,e)))
|
||||||
|
|
||||||
|
|
||||||
|
segment2' :: [Time.Event (Bool, a)] -> [Time.Event (Bool, a)]
|
||||||
|
segment2' es = foldr splitEs es pts
|
||||||
|
where pts = nub $ points es
|
||||||
|
|
||||||
|
splitEs :: Time.Time -> [Time.Event (Bool, a)] -> [Time.Event (Bool, a)]
|
||||||
|
splitEs _ [] = []
|
||||||
|
splitEs t ((ev@(a, (s,e), (h,v))):es) | t > s && t < e = (a, (s,t),(h,v)):(a, (t,e),(False,v)):(splitEs t es)
|
||||||
|
| otherwise = ev:splitEs t es
|
||||||
|
|
||||||
|
whileEvents :: MonadIO m => (Event -> m ()) -> m Bool
|
||||||
|
whileEvents act = do
|
||||||
|
event <- liftIO pollEvent
|
||||||
|
case event of
|
||||||
|
Quit -> return True
|
||||||
|
NoEvent -> return False
|
||||||
|
_ -> do
|
||||||
|
act event
|
||||||
|
whileEvents act
|
||||||
|
|
||||||
|
runLoop :: AppConfig -> Scene -> IO ()
|
||||||
|
runLoop = evalStateT . runReaderT loop
|
||||||
|
|
||||||
|
textSize :: String -> Font -> IO ((Float,Float))
|
||||||
|
textSize text font =
|
||||||
|
do message <- renderTextSolid font text (Color 0 0 0)
|
||||||
|
return (fromScreen (surfaceGetWidth message, surfaceGetHeight message))
|
||||||
|
|
||||||
|
run = do mp <- newMVar silence
|
||||||
|
forkIO $ run' mp
|
||||||
|
return mp
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
--putStrLn $ show scene
|
||||||
|
runLoop env scene
|
||||||
|
|
||||||
|
|
||||||
|
-- colourToPixel :: Colour Double -> Pixel
|
||||||
|
-- colourToPixel c = rgbColor (floor $ 256*r) (floor $ 256* g) (floor $ 256*b)
|
||||||
|
-- where (RGB r g b) = toSRGB c
|
||||||
|
|
||||||
|
colourToPixel :: Colour Double -> Pixel
|
||||||
|
colourToPixel c = rgbColor (floor $ r*255) (floor $ g*255) (floor $ b *255) -- mapRGB (surfaceGetPixelFormat screen) 255 255 255
|
||||||
|
where (RGB r g b) = toSRGB c
|
||||||
|
|
||||||
|
--colourToPixel :: Surface -> Colour Double -> IO Pixel
|
||||||
|
--colourToPixel s c = (mapRGB . surfaceGetPixelFormat) s (floor $ r*255) (floor $ g*255) (floor $ b*255)
|
||||||
|
|
||||||
|
|
||||||
|
fi a = fromIntegral a
|
||||||
|
|
||||||
|
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))
|
||||||
|
|
||||||
|
pixel :: Surface -> (GHC.Word.Word8,GHC.Word.Word8,GHC.Word.Word8) -> IO Pixel
|
||||||
|
pixel surface (r,g,b) = mapRGB (surfaceGetPixelFormat surface) r g b
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
module Sound.Tidal.Vis where
|
||||||
|
|
||||||
|
import qualified Graphics.Rendering.Cairo as C
|
||||||
|
import Data.Colour
|
||||||
|
import Data.Colour.Names
|
||||||
|
import Data.Colour.SRGB
|
||||||
|
import Control.Applicative
|
||||||
|
import Sound.Tidal.Parse
|
||||||
|
import Sound.Tidal.Pattern
|
||||||
|
import Sound.Tidal.Utils
|
||||||
|
import Data.Ratio
|
||||||
|
|
||||||
|
vPDF = v C.withPDFSurface
|
||||||
|
vSVG = v C.withSVGSurface
|
||||||
|
|
||||||
|
v sf fn (x,y) pat =
|
||||||
|
sf fn x y $ \surf -> do
|
||||||
|
C.renderWith surf $ do
|
||||||
|
C.save
|
||||||
|
C.scale x y
|
||||||
|
C.setOperator C.OperatorOver
|
||||||
|
C.setSourceRGB 0 0 0
|
||||||
|
C.rectangle 0 0 1 1
|
||||||
|
C.fill
|
||||||
|
mapM_ renderEvent (events pat)
|
||||||
|
C.restore
|
||||||
|
|
||||||
|
|
||||||
|
vLines sf fn (x,y) pat cyclesPerLine nLines =
|
||||||
|
sf fn x y $ \surf -> do
|
||||||
|
C.renderWith surf $ do
|
||||||
|
C.save
|
||||||
|
C.scale x (y / (fromIntegral nLines))
|
||||||
|
C.setOperator C.OperatorOver
|
||||||
|
C.setSourceRGB 0 0 0
|
||||||
|
C.rectangle 0 0 1 1
|
||||||
|
C.fill
|
||||||
|
mapM_ (\x -> do C.save
|
||||||
|
C.translate 0 (fromIntegral x)
|
||||||
|
drawLine ((cyclesPerLine * (fromIntegral x)) `rotR` pat)
|
||||||
|
C.restore
|
||||||
|
) [0 .. (nLines - 1)]
|
||||||
|
C.restore
|
||||||
|
where drawLine p = mapM_ renderEvent (events (_density cyclesPerLine p))
|
||||||
|
|
||||||
|
|
||||||
|
renderEvent (_, (s,e), (cs)) = do C.save
|
||||||
|
drawBlocks cs 0
|
||||||
|
C.restore
|
||||||
|
where height = 1/(fromIntegral $ length cs)
|
||||||
|
drawBlocks [] _ = return ()
|
||||||
|
drawBlocks (c:cs) n = do let (RGB r g b) = toSRGB c
|
||||||
|
C.setSourceRGBA r g b 1
|
||||||
|
C.rectangle x y w h
|
||||||
|
C.fill
|
||||||
|
C.stroke
|
||||||
|
drawBlocks cs (n+1)
|
||||||
|
where x = (fromRational s)
|
||||||
|
y = (fromIntegral n) * height
|
||||||
|
w = (fromRational (e-s))
|
||||||
|
h = height
|
||||||
|
|
||||||
|
|
||||||
|
events pat = (map (mapSnd' (\(s,e) -> ((s - (ticks/2))/speed,(e - (ticks/2))/speed))) $ arc (segment pat) ((ticks/2), (ticks/2)+speed))
|
||||||
|
where speed = 1
|
||||||
|
ticks = 0
|
||||||
|
--pat = p "[red blue green,orange purple]" :: Sequence ColourD
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
module Sound.Tidal.Vis2 where
|
||||||
|
|
||||||
|
import qualified Graphics.Rendering.Cairo as C
|
||||||
|
import Data.Colour
|
||||||
|
import Data.Colour.Names
|
||||||
|
import Data.Colour.SRGB
|
||||||
|
import Control.Applicative
|
||||||
|
import Sound.Tidal.Parse
|
||||||
|
import Sound.Tidal.Pattern
|
||||||
|
import Sound.Tidal.Time
|
||||||
|
import Sound.Tidal.Utils
|
||||||
|
import Data.Ratio
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Cmd
|
||||||
|
import Data.List
|
||||||
|
import Data.Ord ( comparing )
|
||||||
|
|
||||||
|
totalWidth = 200 :: Double
|
||||||
|
ratio = 2/40
|
||||||
|
levelHeight = totalWidth * ratio
|
||||||
|
|
||||||
|
arrangeEvents [] = []
|
||||||
|
arrangeEvents (e:es) = addEvent e (arrangeEvents es)
|
||||||
|
fits e es = null $ filter (id) $ map (\e' -> isJust $ subArc (snd' e) (snd' e')) es
|
||||||
|
addEvent e [] = [[e]]
|
||||||
|
addEvent e (level:levels) | fits e level = (e:level):levels
|
||||||
|
| otherwise = level:(addEvent e levels)
|
||||||
|
|
||||||
|
v sf fn (x,y) levels =
|
||||||
|
sf fn x y $ \surf -> do
|
||||||
|
C.renderWith surf $ do
|
||||||
|
C.save
|
||||||
|
-- C.scale x (y / (fromIntegral $ length levels))
|
||||||
|
C.setOperator C.OperatorOver
|
||||||
|
-- C.setSourceRGB 0 0 0
|
||||||
|
-- C.rectangle 0 0 1 1
|
||||||
|
--C.fill
|
||||||
|
mapM_ (renderLevel (length levels)) $ enumerate levels
|
||||||
|
C.restore
|
||||||
|
|
||||||
|
renderLevel total (n, level) = do C.save
|
||||||
|
mapM_ drawEvent $ level
|
||||||
|
C.restore
|
||||||
|
where drawEvent ((sWhole, eWhole), (s,e), c) =
|
||||||
|
do let (RGB r g b) = toSRGB c
|
||||||
|
-- C.setSourceRGBA 0.6 0.6 0.6 1
|
||||||
|
-- C.rectangle x y lineW levelHeight
|
||||||
|
C.withLinearPattern xWhole 0 (wholeLineW+xWhole) 0 $ \pattern ->
|
||||||
|
do --C.patternAddColorStopRGB pattern 0 0 0 0
|
||||||
|
--C.patternAddColorStopRGB pattern 0.5 1 1 1
|
||||||
|
C.save
|
||||||
|
C.patternAddColorStopRGBA pattern 0 r g b 1
|
||||||
|
C.patternAddColorStopRGBA pattern 1 r g b 0.5
|
||||||
|
C.patternSetFilter pattern C.FilterFast
|
||||||
|
C.setSource pattern
|
||||||
|
-- C.setSourceRGBA r g b 1
|
||||||
|
--C.arc (x+half) (y+half) (w/2) 0 (2 * pi)
|
||||||
|
C.rectangle x y lineW levelHeight
|
||||||
|
C.fill
|
||||||
|
C.restore
|
||||||
|
-- C.stroke
|
||||||
|
--C.fill
|
||||||
|
-- C.stroke
|
||||||
|
where x = (fromRational s) * totalWidth
|
||||||
|
y = (fromIntegral n) * levelHeight
|
||||||
|
xWhole = (fromRational sWhole) * totalWidth
|
||||||
|
w = levelHeight
|
||||||
|
lineW = ((fromRational $ e-s) * totalWidth)
|
||||||
|
wholeLineW = ((fromRational $ eWhole-sWhole) * totalWidth)
|
||||||
|
lineH = 2
|
||||||
|
lgap = 3
|
||||||
|
rgap = 3
|
||||||
|
border = 3
|
||||||
|
half = levelHeight / 2
|
||||||
|
quarter = levelHeight / 4
|
||||||
|
vPDF = v C.withPDFSurface
|
||||||
|
|
||||||
|
vis name pat = do v (C.withSVGSurface) (name ++ ".svg") (totalWidth, levelHeight*(fromIntegral $ length levels)) levels
|
||||||
|
-- rawSystem "/home/alex/Dropbox/bin/fixsvg.pl" [name ++ ".svg"]
|
||||||
|
-- rawSystem "convert" [name ++ ".svg", name ++ ".pdf"]
|
||||||
|
return ()
|
||||||
|
where levels = arrangeEvents $ sortOn ((\x -> snd x - fst x) . snd') (arc pat (0,1))
|
||||||
|
sortOn f = map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))
|
||||||
|
|
||||||
|
visAsString pat = do vis "/tmp/vis2-tmp" pat
|
||||||
|
svg <- readFile "/tmp/vis2-tmp.svg"
|
||||||
|
return svg
|
||||||
|
|
||||||
|
|
||||||
|
magicallyMakeEverythingFaster = splitArcs 16
|
||||||
|
where splitArcs n p = concatMap (\i -> arc p (i,i+(1/n))) [0, (1/n) .. (1-(1/n))]
|
||||||
|
|
@ -0,0 +1,85 @@
|
||||||
|
module Sound.Tidal.Vis2 where
|
||||||
|
|
||||||
|
import qualified Graphics.Rendering.Cairo as C
|
||||||
|
import Data.Colour
|
||||||
|
import Data.Colour.Names
|
||||||
|
import Data.Colour.SRGB
|
||||||
|
import Control.Applicative
|
||||||
|
import Sound.Tidal.Parse
|
||||||
|
import Sound.Tidal.Pattern
|
||||||
|
import Sound.Tidal.Time
|
||||||
|
import Sound.Tidal.Utils
|
||||||
|
import Data.Ratio
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Cmd
|
||||||
|
import Data.List
|
||||||
|
|
||||||
|
totalWidth = 600 :: Double
|
||||||
|
ratio = 1/40
|
||||||
|
levelHeight = totalWidth * ratio
|
||||||
|
|
||||||
|
arrangeEvents [] = []
|
||||||
|
arrangeEvents (e:es) = addEvent e (arrangeEvents es)
|
||||||
|
fits e es = null $ filter (id) $ map (\e' -> isJust $ subArc (snd' e) (snd' e')) es
|
||||||
|
addEvent e [] = [[e]]
|
||||||
|
addEvent e (level:levels) | fits e level = (e:level):levels
|
||||||
|
| otherwise = level:(addEvent e levels)
|
||||||
|
|
||||||
|
v sf fn (x,y) levels =
|
||||||
|
sf fn x y $ \surf -> do
|
||||||
|
C.renderWith surf $ do
|
||||||
|
C.save
|
||||||
|
-- C.scale x (y / (fromIntegral $ length levels))
|
||||||
|
C.setOperator C.OperatorOver
|
||||||
|
-- C.setSourceRGB 0 0 0
|
||||||
|
-- C.rectangle 0 0 1 1
|
||||||
|
--C.fill
|
||||||
|
mapM_ (renderLevel (length levels)) $ enumerate levels
|
||||||
|
C.restore
|
||||||
|
|
||||||
|
renderLevel total (n, level) = do C.save
|
||||||
|
mapM_ drawEvent $ level
|
||||||
|
C.restore
|
||||||
|
where drawEvent ((sWhole, eWhole), (s,e), c) =
|
||||||
|
do let (RGB r g b) = toSRGB c
|
||||||
|
-- C.setSourceRGBA 0.6 0.6 0.6 1
|
||||||
|
-- C.rectangle x y lineW levelHeight
|
||||||
|
C.withLinearPattern xWhole 0 (wholeLineW+xWhole) 0 $ \pattern ->
|
||||||
|
do --C.patternAddColorStopRGB pattern 0 0 0 0
|
||||||
|
--C.patternAddColorStopRGB pattern 0.5 1 1 1
|
||||||
|
C.save
|
||||||
|
C.patternAddColorStopRGBA pattern 0 r g b 1
|
||||||
|
C.patternAddColorStopRGBA pattern 1 r g b 0.5
|
||||||
|
C.patternSetFilter pattern C.FilterFast
|
||||||
|
C.setSource pattern
|
||||||
|
-- C.setSourceRGBA r g b 1
|
||||||
|
--C.arc (x+half) (y+half) (w/2) 0 (2 * pi)
|
||||||
|
C.rectangle x y lineW levelHeight
|
||||||
|
C.fill
|
||||||
|
C.restore
|
||||||
|
-- C.stroke
|
||||||
|
--C.fill
|
||||||
|
-- C.stroke
|
||||||
|
where x = (fromRational s) * totalWidth
|
||||||
|
y = (fromIntegral n) * levelHeight
|
||||||
|
xWhole = (fromRational sWhole) * totalWidth
|
||||||
|
w = levelHeight
|
||||||
|
lineW = ((fromRational $ e-s) * totalWidth)
|
||||||
|
wholeLineW = ((fromRational $ eWhole-sWhole) * totalWidth)
|
||||||
|
lineH = 2
|
||||||
|
lgap = 3
|
||||||
|
rgap = 3
|
||||||
|
border = 3
|
||||||
|
half = levelHeight / 2
|
||||||
|
quarter = levelHeight / 4
|
||||||
|
vPDF = v C.withPDFSurface
|
||||||
|
|
||||||
|
vis name pat = do v (C.withSVGSurface) (name ++ ".svg") (totalWidth, levelHeight*(fromIntegral $ length levels)) levels
|
||||||
|
rawSystem "/home/alex/Dropbox/bin/fixsvg.pl" [name ++ ".svg"]
|
||||||
|
-- rawSystem "convert" [name ++ ".svg", name ++ ".pdf"]
|
||||||
|
return ()
|
||||||
|
where levels = arrangeEvents $ sortOn ((\x -> snd x - fst x) . snd') (arc pat (0,1))
|
||||||
|
|
||||||
|
visAsString pat = do vis "/tmp/vis2-tmp" pat
|
||||||
|
svg <- readFile "/tmp/vis2-tmp.svg"
|
||||||
|
return svg
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
module Sound.Tidal.VisCycle where
|
||||||
|
|
||||||
|
import qualified Graphics.Rendering.Cairo as C
|
||||||
|
import Data.Colour
|
||||||
|
import Data.Colour.Names
|
||||||
|
import Data.Colour.SRGB
|
||||||
|
import Control.Applicative
|
||||||
|
import Sound.Tidal.Parse
|
||||||
|
import Sound.Tidal.Pattern
|
||||||
|
import Sound.Tidal.Time
|
||||||
|
import Sound.Tidal.Utils
|
||||||
|
import Data.Ratio
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Cmd
|
||||||
|
import Data.List
|
||||||
|
import Data.Ord ( comparing )
|
||||||
|
|
||||||
|
totalWidth = 1080 :: Double
|
||||||
|
ratio = 1
|
||||||
|
|
||||||
|
arrangeEvents [] = []
|
||||||
|
arrangeEvents (e:es) = addEvent e (arrangeEvents es)
|
||||||
|
fits e es = null $ filter (id) $ map (\e' -> isJust $ subArc (snd' e) (snd' e')) es
|
||||||
|
addEvent e [] = [[e]]
|
||||||
|
addEvent e (level:levels) | fits e level = (e:level):levels
|
||||||
|
| otherwise = level:(addEvent e levels)
|
||||||
|
|
||||||
|
v sf fn (x,y) levels =
|
||||||
|
sf fn x y $ \surf -> do
|
||||||
|
C.renderWith surf $ do
|
||||||
|
C.setAntialias C.AntialiasBest
|
||||||
|
C.save
|
||||||
|
C.scale totalWidth totalWidth
|
||||||
|
C.setOperator C.OperatorOver
|
||||||
|
-- C.setSourceRGB 0 0 0
|
||||||
|
-- C.rectangle 0 0 1 1
|
||||||
|
--C.fill
|
||||||
|
mapM_ (renderLevel (length levels)) $ enumerate levels
|
||||||
|
C.restore
|
||||||
|
|
||||||
|
renderLevel total (n, level) = do C.save
|
||||||
|
mapM_ drawEvent $ level
|
||||||
|
C.restore
|
||||||
|
where drawEvent ((sWhole, eWhole), (s,e), c) =
|
||||||
|
do let (RGB r g b) = toSRGB c
|
||||||
|
C.save
|
||||||
|
C.setSourceRGBA r g b 1
|
||||||
|
C.arc 0.5 0.5 (h+levelHeight) ((fromRational s)*(pi*2)-(pi/2)) ((fromRational e)*(pi*2)-(pi/2))
|
||||||
|
C.arcNegative 0.5 0.5 h ((fromRational e)*(pi*2)-(pi/2)) ((fromRational s)*(pi*2)-(pi/2))
|
||||||
|
C.fill
|
||||||
|
C.setSourceRGBA 0.5 0.5 0.5 1
|
||||||
|
C.setLineWidth 0.005
|
||||||
|
C.arc 0.5 0.5 (h+levelHeight) ((fromRational s)*(pi*2)-(pi/2)) ((fromRational e)*(pi*2)-(pi/2))
|
||||||
|
C.arcNegative 0.5 0.5 h ((fromRational e)*(pi*2)-(pi/2)) ((fromRational s)*(pi*2)-(pi/2))
|
||||||
|
C.stroke
|
||||||
|
C.restore
|
||||||
|
where h = levelHeight * (fromIntegral (n + 1))
|
||||||
|
levelHeight = (1 / fromIntegral (total+1))/2
|
||||||
|
vPDF = v C.withPDFSurface
|
||||||
|
|
||||||
|
vis name pat = do v (C.withPDFSurface) (name ++ ".pdf") (totalWidth, totalWidth) levels
|
||||||
|
return ()
|
||||||
|
where levels = arrangeEvents $ sortOn ((\x -> snd x - fst x) . snd') (arc pat (0,1))
|
||||||
|
sortOn f = map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))
|
||||||
|
|
||||||
|
visAsString pat = do vis "/tmp/vis2-tmp" pat
|
||||||
|
svg <- readFile "/tmp/vis2-tmp.svg"
|
||||||
|
return svg
|
||||||
|
|
||||||
|
|
||||||
|
magicallyMakeEverythingFaster = splitArcs 16
|
||||||
|
where splitArcs n p = concatMap (\i -> arc p (i,i+(1/n))) [0, (1/n) .. (1-(1/n))]
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
module Sound.Tidal.VisCycle where
|
||||||
|
|
||||||
|
import qualified Graphics.Rendering.Cairo as C
|
||||||
|
import Data.Colour
|
||||||
|
import Data.Colour.Names
|
||||||
|
import Data.Colour.SRGB
|
||||||
|
import Control.Applicative
|
||||||
|
import Sound.Tidal.Parse
|
||||||
|
import Sound.Tidal.Pattern
|
||||||
|
import Sound.Tidal.Time
|
||||||
|
import Sound.Tidal.Utils
|
||||||
|
import Data.Ratio
|
||||||
|
import Data.Maybe
|
||||||
|
import System.Cmd
|
||||||
|
import Data.List
|
||||||
|
import Data.Ord ( comparing )
|
||||||
|
|
||||||
|
totalWidth = 200 :: Double
|
||||||
|
ratio = 2/40
|
||||||
|
levelHeight = totalWidth * ratio
|
||||||
|
|
||||||
|
arrangeEvents [] = []
|
||||||
|
arrangeEvents (e:es) = addEvent e (arrangeEvents es)
|
||||||
|
fits e es = null $ filter (id) $ map (\e' -> isJust $ subArc (snd' e) (snd' e')) es
|
||||||
|
addEvent e [] = [[e]]
|
||||||
|
addEvent e (level:levels) | fits e level = (e:level):levels
|
||||||
|
| otherwise = level:(addEvent e levels)
|
||||||
|
|
||||||
|
v sf fn (x,y) levels =
|
||||||
|
sf fn x y $ \surf -> do
|
||||||
|
C.renderWith surf $ do
|
||||||
|
C.save
|
||||||
|
-- C.scale x (y / (fromIntegral $ length levels))
|
||||||
|
C.setOperator C.OperatorOver
|
||||||
|
-- C.setSourceRGB 0 0 0
|
||||||
|
-- C.rectangle 0 0 1 1
|
||||||
|
--C.fill
|
||||||
|
mapM_ (renderLevel (length levels)) $ enumerate levels
|
||||||
|
C.restore
|
||||||
|
|
||||||
|
renderLevel total (n, level) = do C.save
|
||||||
|
mapM_ drawEvent $ level
|
||||||
|
C.restore
|
||||||
|
where drawEvent ((sWhole, eWhole), (s,e), c) =
|
||||||
|
do let (RGB r g b) = toSRGB c
|
||||||
|
-- C.setSourceRGBA 0.6 0.6 0.6 1
|
||||||
|
-- C.rectangle x y lineW levelHeight
|
||||||
|
C.withLinearPattern xWhole 0 (wholeLineW+xWhole) 0 $ \pattern ->
|
||||||
|
do --C.patternAddColorStopRGB pattern 0 0 0 0
|
||||||
|
--C.patternAddColorStopRGB pattern 0.5 1 1 1
|
||||||
|
C.save
|
||||||
|
C.patternAddColorStopRGBA pattern 0 r g b 1
|
||||||
|
C.patternAddColorStopRGBA pattern 1 r g b 0.5
|
||||||
|
C.patternSetFilter pattern C.FilterFast
|
||||||
|
C.setSource pattern
|
||||||
|
-- C.setSourceRGBA r g b 1
|
||||||
|
--C.arc (x+half) (y+half) (w/2) 0 (2 * pi)
|
||||||
|
C.rectangle x y lineW levelHeight
|
||||||
|
C.fill
|
||||||
|
C.restore
|
||||||
|
-- C.stroke
|
||||||
|
--C.fill
|
||||||
|
-- C.stroke
|
||||||
|
where x = (fromRational s) * totalWidth
|
||||||
|
y = (fromIntegral n) * levelHeight
|
||||||
|
xWhole = (fromRational sWhole) * totalWidth
|
||||||
|
w = levelHeight
|
||||||
|
lineW = ((fromRational $ e-s) * totalWidth)
|
||||||
|
wholeLineW = ((fromRational $ eWhole-sWhole) * totalWidth)
|
||||||
|
lineH = 2
|
||||||
|
lgap = 3
|
||||||
|
rgap = 3
|
||||||
|
border = 3
|
||||||
|
half = levelHeight / 2
|
||||||
|
quarter = levelHeight / 4
|
||||||
|
vPDF = v C.withPDFSurface
|
||||||
|
|
||||||
|
vis name pat = do v (C.withSVGSurface) (name ++ ".svg") (totalWidth, levelHeight*(fromIntegral $ length levels)) levels
|
||||||
|
-- rawSystem "/home/alex/Dropbox/bin/fixsvg.pl" [name ++ ".svg"]
|
||||||
|
-- rawSystem "convert" [name ++ ".svg", name ++ ".pdf"]
|
||||||
|
return ()
|
||||||
|
where levels = arrangeEvents $ sortOn ((\x -> snd x - fst x) . snd') (arc pat (0,1))
|
||||||
|
sortOn f = map snd . sortBy (comparing fst) . map (\x -> let y = f x in y `seq` (y, x))
|
||||||
|
|
||||||
|
visAsString pat = do vis "/tmp/vis2-tmp" pat
|
||||||
|
svg <- readFile "/tmp/vis2-tmp.svg"
|
||||||
|
return svg
|
||||||
|
|
||||||
|
|
||||||
|
magicallyMakeEverythingFaster = splitArcs 16
|
||||||
|
where splitArcs n p = concatMap (\i -> arc p (i,i+(1/n))) [0, (1/n) .. (1-(1/n))]
|
||||||
Binary file not shown.
|
|
@ -0,0 +1,70 @@
|
||||||
|
%PDF-1.5
|
||||||
|
%µí®û
|
||||||
|
3 0 obj
|
||||||
|
<< /Length 4 0 R
|
||||||
|
/Filter /FlateDecode
|
||||||
|
>>
|
||||||
|
stream
|
||||||
|
xœ<EFBFBD>W[r!ü÷)æ!HÇðò‘ø;Îý«ÂcØi¶\®òöÒ uÏß{”Ÿï¯ãç/{|ý«ŸÉÚòï÷ïãÏGû“Ø ?~øþÐòäC/¶‡¿cÊ¢ <09>£MSW+ÐÈ*Ðopa2ÆŒ£äPclÈ¥‰à@Æ{¤˜Ðøí®ÚŠ6^×£ {"]b‹3P’0FóÓŒœé4ù²X×%ýœcŸà$lЀÉF<C389>XŒÑµ‚×aß%™PÑ<50>‚p&²›`’€É˜##]™™¯EF6Q¹….BªsA÷*rJ¥c°ÌÀï{o¡õbóÕU–N¤H6Î8QÁ„.PmÎWoÜ÷0òqï<71>½~ŸÏ[VôK)ägv€îõK êç|´§~,ößʬõ_ìi`X–•«_,ƒ"HðÅA}—Sg߸·*¨V<C2A8>¡¨†¶ã¥†[y•Ž«3—Äá7]”DÚì;û¢ÌÈÓ”î]<5D>Èl¤O¿¡Á:v/a›YBð1ô±énÔZÛU„hFóÀÍŒú<C592>®¸øæ„Ë•öb^œú~Çî͸™SöÁ¿\ö-'<27>eKZÍXõ¤*0ˆ³p¥Ú4(<28>UÇá”>NìVÉ3%•–<E280A2>ž\¯{òÆueEæ´d9<64>ÝÉp²ÔPo‡C¸Œj«åHú‘!te¼ópä2$<24>Íã²°–Gf~
|
||||||
|
‰²K™|¹<>_€’šž‚M5ƒØß3ŸÖÜ#›ž1«c{pJPt!ÏÞ¢c~½Ä=ùjIqâŽOQDë2bDž<44>’<EFBFBD>ä³'ºŠKJ\\(“GR=1æÖã×LKJdÄl9M!ß·SO´bf‰Ñª¡e3þÚ6ªþe€"¹ö²rnœA
Ž#£ž-rH®ï”ìhËe÷®ÖØ\Ó¥”_ßîôO÷r¢"^ÿZ¥šÄìxýÒÃãD§ÇÇyo¤Èó8rIîhî>ƒâKÛ<4B>ü!E¢lY˽Q«7K}9–räÌç•9²é9rj:¯äÈs@”o˜_Z<>œ¹Ÿ‚$ŠŽµŒ2Ô«OñŠS’äJ©GÉ<47>4èQrt„ŽÝêyX–’…Ð+Þ²|èeÙ³f‡EÕ²Î\Qqr<71>\Õy¦‡9”ŽÝGŸ–¦JÒ†’Î
|
||||||
|
5FNÜBS~~ü~4,Ù
|
||||||
|
endstream
|
||||||
|
endobj
|
||||||
|
4 0 obj
|
||||||
|
893
|
||||||
|
endobj
|
||||||
|
2 0 obj
|
||||||
|
<<
|
||||||
|
/ExtGState <<
|
||||||
|
/a0 << /CA 1 /ca 1 >>
|
||||||
|
>>
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
5 0 obj
|
||||||
|
<< /Type /Page
|
||||||
|
/Parent 1 0 R
|
||||||
|
/MediaBox [ 0 0 300 100 ]
|
||||||
|
/Contents 3 0 R
|
||||||
|
/Group <<
|
||||||
|
/Type /Group
|
||||||
|
/S /Transparency
|
||||||
|
/I true
|
||||||
|
/CS /DeviceRGB
|
||||||
|
>>
|
||||||
|
/Resources 2 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
1 0 obj
|
||||||
|
<< /Type /Pages
|
||||||
|
/Kids [ 5 0 R ]
|
||||||
|
/Count 1
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
6 0 obj
|
||||||
|
<< /Creator (cairo 1.12.16 (http://cairographics.org))
|
||||||
|
/Producer (cairo 1.12.16 (http://cairographics.org))
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
7 0 obj
|
||||||
|
<< /Type /Catalog
|
||||||
|
/Pages 1 0 R
|
||||||
|
>>
|
||||||
|
endobj
|
||||||
|
xref
|
||||||
|
0 8
|
||||||
|
0000000000 65535 f
|
||||||
|
0000001293 00000 n
|
||||||
|
0000001007 00000 n
|
||||||
|
0000000015 00000 n
|
||||||
|
0000000985 00000 n
|
||||||
|
0000001079 00000 n
|
||||||
|
0000001358 00000 n
|
||||||
|
0000001487 00000 n
|
||||||
|
trailer
|
||||||
|
<< /Size 8
|
||||||
|
/Root 7 0 R
|
||||||
|
/Info 6 0 R
|
||||||
|
>>
|
||||||
|
startxref
|
||||||
|
1539
|
||||||
|
%%EOF
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -0,0 +1,47 @@
|
||||||
|
{-# LANGUAGE OverloadedStrings #-}
|
||||||
|
|
||||||
|
import Sound.Tidal.Context
|
||||||
|
import Sound.Tidal.Vis
|
||||||
|
import Data.Colour
|
||||||
|
|
||||||
|
render :: [Pattern ColourD] -> IO ()
|
||||||
|
render xs = mapM_ (\(n, p) -> vPDF (show n ++ ".pdf") (300,100) p) $ zip [0..] xs
|
||||||
|
|
||||||
|
main = do render [a,b,c,d,e,f,g]
|
||||||
|
return ()
|
||||||
|
|
||||||
|
a = density 16 $ every 2 rev $ every 3 (superimpose (iter 4)) $ rev "[black blue darkblue, grey lightblue]"
|
||||||
|
|
||||||
|
b = flip darken <$> "[black blue orange, red green]*16" <*> sinewave1
|
||||||
|
|
||||||
|
c = density 10 $ flip darken
|
||||||
|
<$> "[black blue, grey ~ navy, cornflowerblue blue]*2"
|
||||||
|
<*> (slow 5 $ (*) <$> sinewave1 <*> (slow 2 triwave1))
|
||||||
|
|
||||||
|
d = every 2 rev $ density 10 $ (blend'
|
||||||
|
<$> "blue navy"
|
||||||
|
<*> "orange [red, orange, purple]"
|
||||||
|
<*> (slow 6 $ sinewave1)
|
||||||
|
)
|
||||||
|
where blend' a b c = blend c a b
|
||||||
|
|
||||||
|
e = density 32 $ (flip over
|
||||||
|
<$> ("[grey olive, black ~ brown, darkgrey]")
|
||||||
|
<*> (withOpacity
|
||||||
|
<$> "[beige, lightblue white darkgreen, beige]"
|
||||||
|
<*> ((*) <$> (slow 8 $ slow 4 sinewave1) <*> (slow 3 $ sinewave1)))
|
||||||
|
)
|
||||||
|
|
||||||
|
f = density 2 $ (flip darken
|
||||||
|
<$> (density 8 $ "[black blue, grey ~ navy, cornflowerblue blue]*2")
|
||||||
|
<*> sinewave1
|
||||||
|
)
|
||||||
|
|
||||||
|
g = density 2 $
|
||||||
|
do let x = "[skyblue olive, grey ~ navy, cornflowerblue green]"
|
||||||
|
coloura <- density 8 x
|
||||||
|
colourb <- density 4 x
|
||||||
|
slide <- slow 2 sinewave1
|
||||||
|
return $ blend slide coloura colourb
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
name: tidal-vis
|
||||||
|
version: 0.9.5
|
||||||
|
synopsis: Visual rendering for Tidal patterns
|
||||||
|
-- description:
|
||||||
|
homepage: http://yaxu.org/tidal/
|
||||||
|
license: GPL-3
|
||||||
|
license-file: LICENSE
|
||||||
|
author: Alex McLean
|
||||||
|
maintainer: alex@slab.org
|
||||||
|
Stability: Experimental
|
||||||
|
Copyright: (c) Alex McLean and others, 2017
|
||||||
|
category: Sound
|
||||||
|
build-type: Simple
|
||||||
|
cabal-version: >=1.4
|
||||||
|
|
||||||
|
--Extra-source-files: README.md tidal.el doc/tidal.md doc/tidal.pdf
|
||||||
|
|
||||||
|
Description: Tidal is a domain specific language for live coding pattern. This package allows colour patterns to be rendered as PDF or SVG files.
|
||||||
|
|
||||||
|
library
|
||||||
|
Exposed-modules: Sound.Tidal.Vis
|
||||||
|
Sound.Tidal.Vis2
|
||||||
|
Sound.Tidal.VisCycle
|
||||||
|
|
||||||
|
Build-depends: base < 5, tidal>=0.9.5, colour, cairo, process
|
||||||
Loading…
Reference in New Issue