-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathMain.hs
175 lines (137 loc) · 4.74 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
{-# LANGUAGE Arrows #-}
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleContexts #-}
{-# LANGUAGE OverloadedStrings #-}
module Main where
-- base
import Control.Arrow
import Control.Monad
import Control.Monad.IO.Class
import Control.Monad.Fix (MonadFix)
import Data.Foldable
import Data.Function ((&))
import Data.Functor
import Data.Maybe
import Text.Read (readMaybe)
-- vector-space
import Data.VectorSpace
-- utf8-string
import Data.ByteString.UTF8
-- essence-of-live-coding
import LiveCoding hiding (integrate)
-- essence-of-live-coding-gloss
import LiveCoding.Gloss
-- essence-of-live-coding-pulse
import LiveCoding.Pulse
-- essence-of-live-coding-warp
import LiveCoding.Warp
-- * Main program
main :: IO ()
main = runHandlingStateT $ foreground liveProgram
-- Uncomment the different *RunCells to start different media backends!
liveProgram :: LiveProgram (HandlingStateT IO)
liveProgram = liveCell $ proc _ -> do
-- warpRunCell -< ()
glossRunCell -< ()
-- pulseRunCell -< ()
returnA -< ()
-- * Warp subcomponent
-- | Starts a webserver on port 8080
warpRunCell :: Cell (HandlingStateT IO) () (Maybe RequestInfo)
warpRunCell = runWarpC 8080 warpCell
-- | This handles the incoming request from the webserver
warpCell :: Cell IO ((), Request) (RequestInfo, Response)
warpCell = proc ((), request) -> do
body <- arrM lazyRequestBody -< request
returnA -< (getRequestInfo request, emptyResponse)
-- | The type of interesting data from the request
type RequestInfo = Query
-- | Extract data from the request to use in the rest of the program
getRequestInfo :: Request -> RequestInfo
getRequestInfo = queryString
-- Extend this for a more interesting website
emptyResponse :: Response
emptyResponse = responseLBS
status200
[("Content-Type", "text/plain")]
"Yep, it's working"
-- * Gloss subcomponent
-- ** Backend setup
borderX :: Num a => a
borderX = 300
borderY :: Num a => a
borderY = 400
border :: Num a => (a, a)
border = (borderX, borderY)
glossSettings :: GlossSettings
glossSettings = defaultSettings
{ debugEvents = True
, displaySetting = InWindow "Essence of Live Coding Tutorial" (border ^* 2) (0, 0)
}
-- | Run the gloss backend at 30 frames per second
glossRunCell :: Cell (HandlingStateT IO) () (Maybe ())
glossRunCell = glossWrapC glossSettings $ glossCell
-- & (`withDebuggerC` statePlay) -- Uncomment to display the internal state
-- ** Main gloss cell
-- | This cell is called for every frame of the graphics output
glossCell :: Cell PictureM () ()
glossCell = proc () -> do
events <- constM ask -< ()
ball <- ballSim -< events
addPicture -< ballPic ball
returnA -< ()
-- ** Ball
ballRadius :: Num a => a
ballRadius = 20
-- | Draw the ball in gloss
ballPic :: Ball -> Picture
ballPic Ball { ballPos = (x, y) } = translate x y $ color white $ thickCircle (ballRadius / 2) ballRadius
-- | The type of internal state of the 'ballSim'
data Ball = Ball
{ ballPos :: (Float, Float)
, ballVel :: (Float, Float)
} deriving Data
ballPosX = fst . ballPos
ballPosY = snd . ballPos
ballVelX = fst . ballVel
ballVelY = snd . ballVel
-- | Simulate the position of the ball, given recent events such as mouse clicks
ballSim :: (Monad m, MonadFix m) => Cell m [Event] Ball
ballSim = proc events -> do
rec
let accMouse = sumV $ (^-^ ballPos ball) <$> clicks events
accCollision = sumV $ catMaybes
[ guard (ballPosX ball < - borderX + ballRadius && ballVelX ball < 0)
$> (-2 * ballVelX ball, 0)
, guard (ballPosX ball > borderX - ballRadius && ballVelX ball > 0)
$> (-2 * ballVelX ball, 0)
, guard (ballPosY ball < - borderY + ballRadius && ballVelY ball < 0)
$> (0, -2 * ballVelY ball)
, guard (ballPosY ball > borderY - ballRadius && ballVelY ball > 0)
$> (0, -2 * ballVelY ball)
]
frictionVel <- integrate -< (-0.3) *^ ballVel ball
impulses <- sumS -< sumV [accMouse, 0.97 *^ accCollision]
let newVel = frictionVel ^+^ impulses
newPos <- integrate -< newVel
let ball = Ball newPos newVel
returnA -< ball
-- | Extract the positions of left mouse clicks
clicks :: [Event] -> [(Float, Float)]
clicks = mapMaybe click
click :: Event -> Maybe (Float, Float)
click (EventKey (MouseButton LeftButton) Down _ pos) = Just pos
click _ = Nothing
-- * Pulse subcomponent
-- | Run the PulseAudio backend at 48000 samples per second
pulseRunCell :: Cell (HandlingStateT IO) () [()]
pulseRunCell = pulseWrapC 1600 $ arr (const 440) >>> sawtooth >>> addSample
-- * Utilities
sumS
:: (Monad m, Data v, VectorSpace v)
=> Cell m v v
sumS = foldC (^+^) zeroV
integrate
:: (Monad m, Data v, VectorSpace v, Fractional (Scalar v))
=> Cell m v v
integrate = arr (^/ fromIntegral (stepsPerSecond glossSettings)) >>> sumS