├── .ghci ├── .ghcid ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── LICENSE ├── Main.hs ├── README.md ├── Setup.hs ├── default.nix ├── essence-of-live-coding-tutorial.cabal ├── mypkgs.nix └── shell.nix /.ghci: -------------------------------------------------------------------------------- 1 | :m + LiveCoding LiveCoding.GHCi 2 | :def liveinit liveinit 3 | :def livestep livestep 4 | :def livereload livereload 5 | :def livelaunch livelaunch 6 | :def livestop livestop 7 | -------------------------------------------------------------------------------- /.ghcid: -------------------------------------------------------------------------------- 1 | --warnings --setup=:livelaunch --test=:livereload "--command=cabal repl" 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.txt 2 | *.pdf 3 | *.html 4 | dist/* 5 | dist-newstyle 6 | acmart* 7 | *.png 8 | *.o 9 | *.hi 10 | *.hp 11 | *.prof 12 | *.ps 13 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: haskell 2 | addons: 3 | apt: 4 | packages: 5 | - libgl1-mesa-dev 6 | - libglu1-mesa-dev 7 | - freeglut3-dev 8 | - libpulse-dev 9 | - libblas-dev 10 | - liblapack-dev 11 | cache: 12 | directories: 13 | - $HOME/.cabal 14 | ghc: 15 | - '8.8' 16 | - '8.6' 17 | before-install: 18 | - cabal update 19 | script: 20 | - cabal new-build 21 | - git fetch origin master:origin/master && git rebase origin/master --exec "cabal new-build" 22 | install: skip 23 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Revision history for essence-of-live-coding-tutorial 2 | 3 | ## 0.1.0.0 -- YYYY-mm-dd 4 | 5 | * First version. Released on an unsuspecting world. 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020, Manuel Bärenz 2 | 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without 6 | modification, are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright 9 | notice, this list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above 12 | copyright notice, this list of conditions and the following 13 | disclaimer in the documentation and/or other materials provided 14 | with the distribution. 15 | 16 | * Neither the name of Manuel Bärenz nor the names of other 17 | contributors may be used to endorse or promote products derived 18 | from this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 22 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 23 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 24 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 25 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 26 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 27 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 28 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 29 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 30 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 | -------------------------------------------------------------------------------- /Main.hs: -------------------------------------------------------------------------------- 1 | {-# LANGUAGE Arrows #-} 2 | {-# LANGUAGE DeriveDataTypeable #-} 3 | {-# LANGUAGE FlexibleContexts #-} 4 | {-# LANGUAGE OverloadedStrings #-} 5 | module Main where 6 | 7 | -- base 8 | import Control.Arrow 9 | import Control.Monad 10 | import Control.Monad.IO.Class 11 | import Control.Monad.Fix (MonadFix) 12 | import Data.Foldable 13 | import Data.Function ((&)) 14 | import Data.Functor 15 | import Data.Maybe 16 | import Text.Read (readMaybe) 17 | 18 | -- vector-space 19 | import Data.VectorSpace 20 | 21 | -- utf8-string 22 | import Data.ByteString.UTF8 23 | 24 | -- essence-of-live-coding 25 | import LiveCoding hiding (integrate) 26 | 27 | -- essence-of-live-coding-gloss 28 | import LiveCoding.Gloss 29 | 30 | -- essence-of-live-coding-pulse 31 | import LiveCoding.Pulse 32 | 33 | -- essence-of-live-coding-warp 34 | import LiveCoding.Warp 35 | 36 | -- * Main program 37 | 38 | main :: IO () 39 | main = runHandlingStateT $ foreground liveProgram 40 | 41 | -- Uncomment the different *RunCells to start different media backends! 42 | liveProgram :: LiveProgram (HandlingStateT IO) 43 | liveProgram = liveCell $ proc _ -> do 44 | -- warpRunCell -< () 45 | glossRunCell -< () 46 | -- pulseRunCell -< () 47 | returnA -< () 48 | 49 | -- * Warp subcomponent 50 | 51 | -- | Starts a webserver on port 8080 52 | warpRunCell :: Cell (HandlingStateT IO) () (Maybe RequestInfo) 53 | warpRunCell = runWarpC 8080 warpCell 54 | 55 | -- | This handles the incoming request from the webserver 56 | warpCell :: Cell IO ((), Request) (RequestInfo, Response) 57 | warpCell = proc ((), request) -> do 58 | body <- arrM lazyRequestBody -< request 59 | returnA -< (getRequestInfo request, emptyResponse) 60 | 61 | -- | The type of interesting data from the request 62 | type RequestInfo = Query 63 | 64 | -- | Extract data from the request to use in the rest of the program 65 | getRequestInfo :: Request -> RequestInfo 66 | getRequestInfo = queryString 67 | 68 | -- Extend this for a more interesting website 69 | emptyResponse :: Response 70 | emptyResponse = responseLBS 71 | status200 72 | [("Content-Type", "text/plain")] 73 | "Yep, it's working" 74 | 75 | -- * Gloss subcomponent 76 | 77 | -- ** Backend setup 78 | 79 | borderX :: Num a => a 80 | borderX = 300 81 | 82 | borderY :: Num a => a 83 | borderY = 400 84 | 85 | border :: Num a => (a, a) 86 | border = (borderX, borderY) 87 | 88 | glossSettings :: GlossSettings 89 | glossSettings = defaultSettings 90 | { debugEvents = True 91 | , displaySetting = InWindow "Essence of Live Coding Tutorial" (border ^* 2) (0, 0) 92 | } 93 | 94 | -- | Run the gloss backend at 30 frames per second 95 | glossRunCell :: Cell (HandlingStateT IO) () (Maybe ()) 96 | glossRunCell = glossWrapC glossSettings $ glossCell 97 | -- & (`withDebuggerC` statePlay) -- Uncomment to display the internal state 98 | 99 | -- ** Main gloss cell 100 | 101 | -- | This cell is called for every frame of the graphics output 102 | glossCell :: Cell PictureM () () 103 | glossCell = proc () -> do 104 | events <- constM ask -< () 105 | ball <- ballSim -< events 106 | addPicture -< ballPic ball 107 | returnA -< () 108 | 109 | -- ** Ball 110 | 111 | ballRadius :: Num a => a 112 | ballRadius = 20 113 | 114 | -- | Draw the ball in gloss 115 | ballPic :: Ball -> Picture 116 | ballPic Ball { ballPos = (x, y) } = translate x y $ color white $ thickCircle (ballRadius / 2) ballRadius 117 | 118 | -- | The type of internal state of the 'ballSim' 119 | data Ball = Ball 120 | { ballPos :: (Float, Float) 121 | , ballVel :: (Float, Float) 122 | } deriving Data 123 | 124 | ballPosX = fst . ballPos 125 | ballPosY = snd . ballPos 126 | ballVelX = fst . ballVel 127 | ballVelY = snd . ballVel 128 | 129 | -- | Simulate the position of the ball, given recent events such as mouse clicks 130 | ballSim :: (Monad m, MonadFix m) => Cell m [Event] Ball 131 | ballSim = proc events -> do 132 | rec 133 | let accMouse = sumV $ (^-^ ballPos ball) <$> clicks events 134 | accCollision = sumV $ catMaybes 135 | [ guard (ballPosX ball < - borderX + ballRadius && ballVelX ball < 0) 136 | $> (-2 * ballVelX ball, 0) 137 | , guard (ballPosX ball > borderX - ballRadius && ballVelX ball > 0) 138 | $> (-2 * ballVelX ball, 0) 139 | , guard (ballPosY ball < - borderY + ballRadius && ballVelY ball < 0) 140 | $> (0, -2 * ballVelY ball) 141 | , guard (ballPosY ball > borderY - ballRadius && ballVelY ball > 0) 142 | $> (0, -2 * ballVelY ball) 143 | ] 144 | frictionVel <- integrate -< (-0.3) *^ ballVel ball 145 | impulses <- sumS -< sumV [accMouse, 0.97 *^ accCollision] 146 | let newVel = frictionVel ^+^ impulses 147 | newPos <- integrate -< newVel 148 | let ball = Ball newPos newVel 149 | returnA -< ball 150 | 151 | -- | Extract the positions of left mouse clicks 152 | clicks :: [Event] -> [(Float, Float)] 153 | clicks = mapMaybe click 154 | 155 | click :: Event -> Maybe (Float, Float) 156 | click (EventKey (MouseButton LeftButton) Down _ pos) = Just pos 157 | click _ = Nothing 158 | 159 | -- * Pulse subcomponent 160 | 161 | -- | Run the PulseAudio backend at 48000 samples per second 162 | pulseRunCell :: Cell (HandlingStateT IO) () [()] 163 | pulseRunCell = pulseWrapC 1600 $ arr (const 440) >>> sawtooth >>> addSample 164 | 165 | -- * Utilities 166 | 167 | sumS 168 | :: (Monad m, Data v, VectorSpace v) 169 | => Cell m v v 170 | sumS = foldC (^+^) zeroV 171 | 172 | integrate 173 | :: (Monad m, Data v, VectorSpace v, Fractional (Scalar v)) 174 | => Cell m v v 175 | integrate = arr (^/ fromIntegral (stepsPerSecond glossSettings)) >>> sumS 176 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Tutorial for `essence-of-live-coding` 2 | 3 | ## About the library 4 | 5 | essence-of-live-coding is a general purpose and type safe live coding framework in Haskell. 6 | You can run programs in it, and edit, recompile and reload them _while_ they're running. 7 | Internally, the state of the live program is automatically migrated when performing hot code swap. 8 | 9 | The library also offers an easy to use FRP interface. 10 | It is parametrized by its side effects, 11 | separates data flow cleanly from control flow, 12 | and allows to develop live programs from reusable, modular components. 13 | There are also useful utilities for debugging and quickchecking. 14 | 15 | ### Learn more here 16 | 17 | * https://github.com/turion/essence-of-live-coding 18 | * https://www.manuelbaerenz.de/#computerscience 19 | * https://www.manuelbaerenz.de/essence-of-live-coding/EssenceOfLiveCoding.pdf 20 | 21 | ## Installation 22 | 23 | 1. Clone this repository and enter it: 24 | ``` 25 | git clone https://github.com/turion/essence-of-live-coding-tutorial 26 | cd essence-of-live-coding-tutorial 27 | ``` 28 | 2. Either install [`nix`](https://nixos.org/download.html) and launch a `nix-shell`, 29 | or install the [external dependencies listed below](#external-dependencies) 30 | 3. `cabal update` 31 | (A new version of the library was released recently) 32 | 4. Sanity check: Launch `cabal repl` and close it again. 33 | This should succeed without errors. 34 | 5. Open `Main.hs` in an editor. 35 | 6. Run `ghcid` from the console. 36 | 37 | You should now be seeing a window containing a solid circle (a ball). 38 | If you click anywhere in the window, the ball will start to move in that direction. 39 | Once you make changes to `Main.hs`, 40 | it will automatically reload. 41 | 42 | ### External dependencies 43 | 44 | * Ideally, a Linux system. 45 | (See [below](#non-linux-systems).) 46 | * A standard Haskell development environment, including `cabal` and `ghci`. 47 | (`stack` is not needed.) 48 | Supported GHC versions are 8.6 and 8.8. 49 | * [`ghcid`](https://github.com/ndmitchell/ghcid). 50 | * OpenGL development libraries and PulseAudio development libraries. 51 | (For other sound setups, see [below](#sound-support).) 52 | In Debian-based systems, this amounts to installing these packages: 53 | `libgl1-mesa-dev` `libglu1-mesa-dev` `freeglut3-dev` `libpulse-dev` `libblas-dev` `liblapack-dev` 54 | 55 | ### Nix cache & nixpkgs versions 56 | 57 | * The `nix-shell` is pinned to an up-to-date version of `nixos-unstable`. 58 | This may cause a long build. 59 | You can alleviate that by using `cachix`, 60 | or using a different `nixpkgs` version. 61 | * If you use [`cachix`](https://cachix.org/), 62 | you can speed up your build by using `cachix use manuelbaerenz`. 63 | I'm uploading build artifacts there. 64 | * To use a different `nixpkgs`, edit the first lines of `mypkgs.nix`, and re-run `nix-shell`. 65 | 66 | ### Non-Linux systems 67 | 68 | #### Windows 69 | 70 | I cannot give Windows support for graphics, 71 | since I don't have a Windows machine. 72 | If you have a Windows machine, 73 | you'd like to get graphics to run on your machine, 74 | and you're willing to test a backend and setup with me, 75 | please contact me via a Github issue. 76 | 77 | #### macOS 78 | 79 | Graphics/OpenGL support on macOS seems to be broken, 80 | see https://github.com/turion/essence-of-live-coding-tutorial/issues/3. 81 | If you know how to fix such an issue, please comment, 82 | and we'll resolve it so you can use graphics on macOS. 83 | 84 | ### Sound support 85 | 86 | Currently, I only have audio support ready for Linux, PulseAudio, since this is the platform on which I develop. 87 | If you have a different system, we will still be able to get sound working if you know of good Haskell bindings to your sound system. 88 | If that is the case, please open an issue on https://github.com/turion/essence-of-live-coding/issues so we can prepare a sound backend before the tutorial. 89 | 90 | Either way, the tutorial will focus mainly on video, and only add further backends as time permits. 91 | 92 | ## Helpful resources during the tutorial 93 | 94 | * Basics of composable vector graphics in Gloss: http://hackage.haskell.org/package/gloss-1.13.1.2/docs/Graphics-Gloss-Data-Picture.html 95 | * LiveCoding reference documentation: 96 | * https://hackage.haskell.org/package/essence-of-live-coding 97 | * https://hackage.haskell.org/package/essence-of-live-coding-gloss 98 | * https://hackage.haskell.org/package/essence-of-live-coding-pulse 99 | * https://hackage.haskell.org/package/essence-of-live-coding-warp 100 | * Vector space operations: http://hackage.haskell.org/package/vector-space-0.16/docs/Data-VectorSpace.html 101 | * Web Application Interface reference documentation: 102 | * https://hackage.haskell.org/package/wai-3.2.2.1/docs/Network-Wai.html 103 | 104 | ## Tasks during the tutorial 105 | 106 | | Task | Branch | 107 | | ---------------------------------------- | ----------------------------------------------------------------------------------------------------------------- | 108 | | Magnet (Ball stops when it is very slow) | [progress1_magnet](https://github.com/turion/essence-of-live-coding-tutorial/tree/progress1_magnet) | 109 | | Goal hole with high friction | [progress2_goal](https://github.com/turion/essence-of-live-coding-tutorial/tree/progress2_goal) | 110 | | Obstacle type | [progress3_obstacle_type](https://github.com/turion/essence-of-live-coding-tutorial/tree/progress3_obstacle_type) | 111 | | Draw obstacles, simulate repulsion | [progress4_obstacles](https://github.com/turion/essence-of-live-coding-tutorial/tree/progress4_obstacles) | 112 | | Connect warp backend, print last query | [progress5_connect_warp](https://github.com/turion/essence-of-live-coding-tutorial/tree/progress5_connect_warp) | 113 | | Parse warp impulse query | [progress6_warp_impulse](https://github.com/turion/essence-of-live-coding-tutorial/tree/progress6_warp_impulse) | 114 | | Stretch goal: Add sound | [solution_pulse](https://github.com/turion/essence-of-live-coding-tutorial/tree/solution_pulse) | 115 | -------------------------------------------------------------------------------- /Setup.hs: -------------------------------------------------------------------------------- 1 | import Distribution.Simple 2 | main = defaultMain 3 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let mypkgs = import ./mypkgs.nix {}; 2 | in 3 | mypkgs.essence-of-live-coding-tutorial 4 | -------------------------------------------------------------------------------- /essence-of-live-coding-tutorial.cabal: -------------------------------------------------------------------------------- 1 | cabal-version: >=1.10 2 | 3 | name: essence-of-live-coding-tutorial 4 | version: 0.2.4 5 | synopsis: Tutorial application for the library essence-of-live-coding. 6 | description: 7 | essence-of-live-coding is a general purpose and type safe live coding framework. 8 | . 9 | You can run programs in it, and edit, recompile and reload them while they're running. 10 | Internally, the state of the live program is automatically migrated when performing hot code swap. 11 | . 12 | The library also offers an easy to use FRP interface. 13 | It is parametrized by its side effects, 14 | separates data flow cleanly from control flow, 15 | and allows to develop live programs from reusable, modular components. 16 | There are also useful utilities for debugging and quickchecking. 17 | . 18 | This package provides an executable that serves as the scaffolding for a live coding tutorial at ICFP 2020. 19 | 20 | homepage: https://www.manuelbaerenz.de/#computerscience 21 | category: FRP, Live coding 22 | author: Manuel Bärenz 23 | maintainer: programming@manuelbaerenz.de 24 | license: BSD3 25 | license-file: LICENSE 26 | build-type: Simple 27 | extra-source-files: CHANGELOG.md 28 | extra-doc-files: README.md 29 | 30 | flag pulse 31 | description: Build with pulse audio backend on linux. 32 | 33 | source-repository head 34 | type: git 35 | location: git@github.com:turion/essence-of-live-coding-tutorial.git 36 | 37 | executable essence-of-live-coding-tutorial 38 | main-is: Main.hs 39 | build-depends: 40 | base >= 4.11 && < 5 41 | , gloss >= 1.13 42 | , vector-space >= 0.16 43 | , bytestring >= 0.10 44 | , utf8-string >= 1.0.1 45 | , essence-of-live-coding >= 0.2.4 46 | , essence-of-live-coding-gloss >= 0.2.4 47 | , essence-of-live-coding-warp >= 0.2.4 48 | 49 | if os(linux) && flag(pulse) 50 | build-depends: 51 | essence-of-live-coding-pulse >= 0.2.4 52 | 53 | default-language: Haskell2010 54 | -------------------------------------------------------------------------------- /mypkgs.nix: -------------------------------------------------------------------------------- 1 | { compiler ? "ghc884" 2 | # Leave the next line to use a fairly recent nixos-unstable. 3 | , nixpkgs ? import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs-channels/archive/16fc531784ac226fb268cc59ad573d2746c109c1.tar.gz") {} 4 | # Comment the above and uncomment the following for bleeding-edge nixos-unstable. You might want to `cachix use manuelbaerenz` or wait a long time for builds. 5 | # , nixpkgs ? import (builtins.fetchTarball "https://github.com/NixOS/nixpkgs-channels/archive/c59ea8b8a0e7f927e7291c14ea6cd1bd3a16ff38.tar.gz") {} 6 | # If you have a nix-channel installed locally which you want to use, uncomment and possibly edit the following line, and comment the lines above. 7 | # , nixpkgs ? import {} 8 | }: 9 | 10 | let 11 | inherit (nixpkgs) pkgs; 12 | 13 | essence-of-live-coding = haskellPackages.callHackageDirect { 14 | pkg = "essence-of-live-coding"; 15 | ver = "0.2.4"; 16 | sha256 = "08pjcsjwsb13ld4f1r1kkzmssx6rfv3qsgnwn17i1ag6dqjq5hrd"; 17 | } {}; 18 | essence-of-live-coding-gloss = haskellPackages.callHackageDirect { 19 | pkg = "essence-of-live-coding-gloss"; 20 | ver = "0.2.4"; 21 | sha256 = "03rwwn8a3sjl97mznbymm512j1jak647rawf7pr6cvaz5ljg6kw3"; 22 | } {}; 23 | essence-of-live-coding-pulse = haskellPackages.callHackageDirect { 24 | pkg = "essence-of-live-coding-pulse"; 25 | ver = "0.2.4"; 26 | sha256 = "10w6pzi7bazk4h8ci67pwg2fl71kwxhf2vbpdalds37dxiq3a6p0"; 27 | } {}; 28 | essence-of-live-coding-warp = haskellPackages.callHackageDirect { 29 | pkg = "essence-of-live-coding-warp"; 30 | ver = "0.2.4"; 31 | sha256 = "01am1azb62qsq49cky6accpbxmr7c81ci1jkrb3gjp9c3jcs9kkv"; 32 | } {}; 33 | 34 | haskellPackages = pkgs.haskell.packages.${compiler}.override { 35 | overrides = self: super: { 36 | inherit 37 | essence-of-live-coding 38 | essence-of-live-coding-gloss 39 | essence-of-live-coding-pulse 40 | essence-of-live-coding-warp 41 | ; 42 | http-client = super.http-client_0_7_1; 43 | }; 44 | }; 45 | 46 | myPkgs = haskellPackages.extend (pkgs.haskell.lib.packageSourceOverrides { 47 | essence-of-live-coding-tutorial = ./.; 48 | # Uncomment the following lines if you have forked essence-of-live-coding and insert the appropriate path 49 | # essence-of-live-coding = ../essence-of-live-coding/essence-of-live-coding; 50 | # essence-of-live-coding-gloss = ../essence-of-live-coding/essence-of-live-coding-gloss; 51 | # essence-of-live-coding-warp = ../essence-of-live-coding/essence-of-live-coding-warp; 52 | }); 53 | in 54 | myPkgs 55 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | let myPkgs = import ./mypkgs.nix {}; 2 | in myPkgs.shellFor { 3 | packages = p: with p; [ 4 | essence-of-live-coding-tutorial 5 | ]; 6 | buildInputs = with myPkgs; [ 7 | cabal-install 8 | ghcid 9 | ghcide 10 | hlint 11 | # Add further build tools as you like 12 | ]; 13 | } 14 | --------------------------------------------------------------------------------