packages feed

hylogen 0.1.0.2 → 0.1.0.3

raw patch · 5 files changed

+350/−17 lines, 5 filesdep +bytestringdep +networkPVP: major bump suggested

API removals or changes: PVP suggests a major version bump

Dependencies added: bytestring, network

API changes (from Hackage documentation)

- Hylogen.Globals: pi :: Vec1

Files

README.md view
@@ -2,4 +2,37 @@  Hylogen is a tiny language [embedded in Haskell](https://wiki.haskell.org/Embedded_domain_specific_language) for live-coding visuals. +## Setup+```+cabal update+cabal install ghcid hylogen+```+## Usage++```haskell+-- ./Main.hs+module Main where++import Hylogen+++main = putStrLn . toGLSL $ Vec4 (a, a, a, 1)+  where+    a = cos(X uv * sin(time/ 10) * 10 + X mouse)+      + sin(Y uv * sin(time / 10) * 10 + Y mouse)+```++#### run hylogen++```+hylogen Main.hs+```++#### play!+Go to [localhost:5678](http://localhost:5678) in your browser.++Changes in `Main.hs` will now be propagated in realtime to your shader!++## inspiration+- [The_Force](https://github.com/shawnlawson/The_Force). 
app/Main.hs view
@@ -2,18 +2,29 @@ {-# LANGUAGE ViewPatterns #-} {-# LANGUAGE LambdaCase #-} -import Data.Monoid++import Paths_hylogen (getDataFileName)+import Control.Monad+import Control.Concurrent+import qualified Data.ByteString.Char8 as BS8+import Data.ByteString (ByteString)+import Network (PortID(..), withSocketsDo, listenOn)+import Network.Socket (accept, sClose)+import Network.Socket.ByteString (sendAll)++-- import Data.Monoid import qualified Data.Text as T import Network.WebSockets import System.Environment (getArgs) import System.FilePath import System.INotify import System.Process-import System.Random+-- import System.Random  main :: IO () main = getArgs >>= \case-   [pathToWatch] ->+   [pathToWatch] -> do+      _ <- forkIO $ serveIndex       runServer "127.0.0.1" 8080 $ handleConnection pathToWatch    _ -> error "Name a file to watch!" @@ -25,7 +36,7 @@    connection <- acceptRequest pending    (sendTextData connection . T.pack) =<< getNewSource pathToWatch    -- withINotify $ \inotify ->-   addWatch inotify [Modify] dirToWatch $ \case+   _ <- addWatch inotify [Modify] dirToWatch $ \case       Modified False (Just f) | f == fileToWatch ->          (sendTextData connection . T.pack) =<< getNewSource pathToWatch       _ -> return ()@@ -38,17 +49,17 @@ getNewSource :: FilePath -> IO String getNewSource pathToWatch = do    -- TODO: more robust paths!:-   c <- readFile pathToWatch-   -- let (dirToWatch, fileToWatch) = splitFileName pathToWatch-   -- c <- readProcess "runghc" [-   --      "-i"++dirToWatch-   --    , pathToWatch-   --    ] ""+   -- c <- readFile pathToWatch+   let (dirToWatch, fileToWatch) = splitFileName pathToWatch+   c <- readProcess "runghc" [+        "-i"++dirToWatch+      , pathToWatch+      ] ""    putStrLn c    return c {-    color <- randomRIO (0::Float, 1)-   +    return $ unlines [         "precision mediump float;"       , "uniform float time;"@@ -61,3 +72,24 @@       , "}"       ] -}+++serveIndex :: IO ()+serveIndex = withSocketsDo $ do+   htmlString <- readFile =<< getDataFileName "app/index.html"+   sock <- listenOn $ PortNumber 5678+   forever $ do+      (conn, _) <- accept sock+      _ <- forkIO $ do+         sendAll conn $ wrapHtml $ BS8.pack htmlString+         sClose conn+      return ()++wrapHtml :: ByteString -> ByteString+wrapHtml bs = mconcat [+     "HTTP/1.0 200 OK\r\nContent-Length: "+   , BS8.pack . show $ BS8.length bs+   , "\r\n\r\n", bs, "\r\n"+   ]++
+ app/index.html view
@@ -0,0 +1,264 @@+<!DOCTYPE html>+<html>+  <head>+    <title>HYLOGEN</title>+    <style>+     html, body {+         width: 100%;+         height: 100%;+         margin: 0;+         padding: 0;+         overflow: hidden;+     }+     body {+         display: flex;+         justify-content: center;+         align-items: center;+     }+     #canvas {+         width: 100vmax;+         height: 100vmax;+         background: white;+     }+     #landing {+         width: 100vw;+         height: 100vh;+         position: fixed;+         right: 0;+         top: 0;+         color: white;+         display: flex;+         justify-content: center;+         align-items: center;+         font-family: arial;+         line-height: 10em;+         pointer-events: none;+     }+     #bg {+         background: black;+         width: 100vw;+         height: 100vh;+         position: fixed;+         right: 0;+         top: 0;+         pointer-events: none;+     }++     #title {+         text-transform: uppercase;+         font-style: italic;+         font-weight: bold;+         font-size: 15vw;+         letter-spacing: 0.1em;+         text-shadow: 0 0 0.2em white;+         pointer-events: auto;+     }+     @keyframes fadeOut {+         0% {opacity: 1;}+         100% {opacity: 0;} +     } +     .removing {+         -webkit-animation: fadeOut 1000ms;+         -moz-animation: fadeOut 1000ms;+         animation: fadeOut 1000ms;+     }+     a {+         text-decoration: inherit;+     }++     a:visited {+         color: inherit;+     }+    </style>+  </head>+  <body>+    <div id="bg"> </div>+    <div id="landing">+      <div id="title">+        <a href="https://github.com/sleexyz/hylogen">+          Hylogen+        </a>+      </div>+    </div>++    <canvas id="canvas"></canvas>+    <script>+const vsSource = `+attribute vec3 aPosition;+varying vec3 uv;+void main() {+  gl_Position = vec4(aPosition, 1.0);+  uv = aPosition;+}+`;++const initialFsSource = `+precision mediump float;+uniform float time;+uniform vec3 mouse;+const float PI = 3.141592653589793238462643383;+varying vec3 uv;++void main() {+    gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);+}+`;+++function loadProgram (gl, state, vsSource, fsSource) {++  // compileShader :: (gl, source, shaderType) -> Shader+  // throws Error on compilation error++  function compileShader (gl, source, shaderType) {+    //assert(shaderType === gl.FRAGMENT_SHADER || shaderType === g.VERTEXT_SHADER);++    let shader = gl.createShader(shaderType);++    gl.shaderSource(shader, source);+    gl.compileShader(shader);+++    let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);+    if (!success) {+      throw "could not compile shader:" + gl.getShaderInfoLog(shader);+    }++    return shader;+  };++++  let vs = compileShader(gl, vsSource, gl.VERTEX_SHADER);+  let fs = compileShader(gl, fsSource, gl.FRAGMENT_SHADER);+++  let program = gl.createProgram();++  gl.attachShader(program, vs);+  gl.attachShader(program, fs);++  gl.linkProgram(program);++  let success = gl.getProgramParameter(program, gl.LINK_STATUS);+  if (!success) {+    throw ("program failed to link:" + gl.getProgramInfoLog(program));+  }++  gl.useProgram(program);+++  // Create a square as a strip of two triangles.+  gl.bindBuffer(gl.ARRAY_BUFFER, gl.createBuffer());+  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([ -1,1,0, 1,1,0, -1,-1,0, 1,-1,0 ]), gl.STATIC_DRAW);++  // Assign attribute aPosition to each of the square's vertices.+  gl.aPosition = gl.getAttribLocation(program, "aPosition");+  gl.enableVertexAttribArray(gl.aPosition);+  gl.vertexAttribPointer(gl.aPosition, 3, gl.FLOAT, false, 0, 0);++  // remember the address within the fragment shader of each of my uniforms variables+  gl.time = gl.getUniformLocation(program, "time");+  gl.mouse = gl.getUniformLocation(program, "mouse");++  draw(gl, state);++  if (state.animationFrameRequest === null) {+    state.animationFrameRequest = requestAnimationFrame(() => animate(gl, state));+  }+}++function draw (gl, state) {++  gl.uniform1f(gl.time, (new Date().getTime() / 1000 - state.time0));+  gl.uniform3f(gl.mouse, state.mouse.x, state.mouse.y, state.mouse.z);++  gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);+};++function animate (gl, state) {+  draw(gl, state);+  state.animationFrameRequest = requestAnimationFrame(() => animate(gl, state));+};++++function setupState (state, canvas){+  function setMouse (event, z) {+    let r = event.target.getBoundingClientRect();+    state.mouse.x = (event.clientX - r.left) / (r.right - r.left) * 2 -1;+    state.mouse.y = (event.clientY - r.bottom) / (r.top - r.bottom) * 2 - 1;++    if (z !== undefined) {+      state.mouse.z = z;+    }+  };++  state.animationFrameRequest = null;++  state.time0 = new Date() / 1000;+++  canvas.onmousedown = (event) => setMouse(event, 1);+  canvas.onmousemove = (event) => setMouse(event);+  canvas.onmouseup = (event) => setMouse(event, 0);++  state.mouse = {x: 0, y: 0, z: 0};++  // TODO: implement Audio+}+++++// stateful variables++const canvas = document.getElementById("canvas");+canvas.width = Math.max(window.innerHeight, window.innerWidth);+canvas.height = Math.max(window.innerHeight, window.innerWidth);++const gl = canvas.getContext("webgl");++const state = {};++setupState(state, canvas);+++loadProgram (gl, state,  vsSource, initialFsSource);+++function fadeOut(elem) {+  elem.className = "removing";+  window.setTimeout(function() {+    elem.remove();+  }, 1000);+}++function connectToHylogen() {+  const wsConn = new WebSocket("ws://localhost:8080/");++  wsConn.onopen = function() {+    const landing = document.getElementById("landing");+    if (landing) {+      fadeOut(document.getElementById("bg"));+      window.setTimeout(() => {fadeOut(landing);}, 500);+    }+    console.log('websocket opened');++  };++  wsConn.onclose = function() {+    console.log('websocket closed');+    window.setTimeout(connectToHylogen, 100);+  };++  wsConn.onmessage = function (m) {+    console.log(m.data);+    loadProgram(gl, state,  vsSource, m.data);+  };+}++connectToHylogen();+    </script>+  </body>+</html>
hylogen.cabal view
@@ -1,5 +1,5 @@ name:                hylogen-version:             0.1.0.2+version:             0.1.0.3 synopsis:            a tiny EDSL for live-coding fragment shaders description:         a tiny EDSL for live-coding fragment shaders homepage:            https://github.com/sleexyz/hylogen@@ -9,6 +9,7 @@ category:            Graphics build-type:          Simple extra-source-files:  README.md+data-files:          app/index.html cabal-version:       >=1.10 stability:           experimental @@ -24,11 +25,13 @@ executable hylogen   main-is:             Main.hs   build-depends:       base-                     , websockets-                     , hinotify-                     , text+                     , bytestring                      , filepath-                     , random+                     , hinotify+                     , network                      , process+                     , random+                     , text+                     , websockets   hs-source-dirs:      app   default-language:    Haskell2010
src/Hylogen/Globals.hs view
@@ -7,7 +7,7 @@  -- | Vec1: -pi = V1u "pi"+time :: Vec1 time = V1u "time" -- rand_ = V1uop "rand" @@ -20,6 +20,7 @@  -- | Vec2: +uv, mouse :: Vec2 uv = V2u "uv" mouse = V2u "mouse"