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 +33/−0
- app/Main.hs +43/−11
- app/index.html +264/−0
- hylogen.cabal +8/−5
- src/Hylogen/Globals.hs +2/−1
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"