git.s-ol.nu mmm / e7b84a6
Split root out of mmm repo s-ol 1 year, 7 months ago
513 changed file(s) with 2 addition(s) and 7621 deletion(s). Raw diff Collapse all Expand all
+0
-10
root/$order less more
0 about
1 blog
2 research
3 workshops
4 games
5 projects
6 experiments
7 meta
8 portfolio
9 static
+0
-6
root/about/text$markdown.md less more
0 about me
1 ========
2 i am s-ol bekic, a designer and creative technologist currently based in milano.
3 if you are looking for an overview over my work or skillset, take a look at my [portfolio][portfolio].
4
5 [portfolio]: /portfolio/
+0
-12
root/blog/$order less more
0 humane_filesystems
1 ludum_dare_33_postmortem
2 video_synth_research
3 love_lua_photoshop_and_games
4 clocks_triggers_gates
5 stretching_gates
6 stencils_101
7 aspect_ratios
8 automating_my_rice
9 challenging_myself
10 self-hosted_virtual_home
11 why_redirectly
+0
-1
root/blog/aspect_ratios/$order less more
0 interactive
+0
-1
root/blog/aspect_ratios/date: time$iso8601-date less more
0 2019-07-12
+0
-119
root/blog/aspect_ratios/interactive/_base: text$moonscript -> table.moon less more
0 import div, a from require 'mmm.dom'
1 import interactive_link from (require 'mmm.mmmfs.util') require 'mmm.dom'
2
3 if MODE ~= 'CLIENT'
4 class Dummy
5 render: =>
6 div {
7 style:
8 position: 'relative'
9 resize: 'horizontal'
10 overflow: 'hidden'
11
12 width: '480px'
13 height: '270px'
14 'min-width': '270px'
15 'max-width': '100%'
16
17 margin: 'auto'
18 padding: '10px'
19 boxSizing: 'border-box'
20 background: 'var(--gray-bright)'
21
22 interactive_link '(click here for the interactive version of this article)'
23 }
24
25 return {
26 UIDemo: Dummy
27 Example: Dummy
28 }
29
30 import CanvasApp from require 'mmm.canvasapp'
31
32 class UIDemo extends CanvasApp
33 width: nil
34 height: nil
35 new: () =>
36 super!
37
38 @canvas.width = nil
39 @canvas.height = nil
40 @canvas.style.width = '100%'
41 @canvas.style.height = '100%'
42 @canvas.style.border = '2px solid var(--gray-dark)'
43
44 @node = div @canvas, style: {
45 position: 'relative'
46 resize: 'horizontal'
47 overflow: 'hidden'
48
49 width: '480px'
50 height: '270px'
51 minWidth: '270px'
52 maxWidth: '100%'
53
54 margin: 'auto'
55 padding: '10px'
56 boxSizing: 'border-box'
57 }
58
59 -- match size of current parent element and return it (for interactive resizing in the demo)
60 updateSize: =>
61 { :clientWidth, :clientHeight } = @canvas.parentElement
62 @canvas.width, @canvas.height = clientWidth, clientHeight
63 @canvas.width, @canvas.height
64
65 -- set up a coordinate system such that the virtual viewport
66 -- of size (w, h) is centered on (0,0) and fills the canvas
67 -- returns remaining margins on the two axes
68 fit: (w, h) =>
69 width, height = @updateSize!
70 @ctx\translate width/2, height/2
71
72 -- maximum scale without cropping either axis
73 scale = math.min (width/w), (height/h)
74 @ctx\scale scale, scale
75
76 -- calculate remaining space on x/y axis
77 rx = (width/scale) - w
78 ry = (height/scale) - h
79
80 -- margins are half of the remaining space
81 rx / 2, ry / 2
82
83 strokeRect: (cx, cy, w, h) =>
84 lw = @ctx.lineWidth / 2
85 @ctx\strokeRect cx - w/2 + lw, cy - h/2 + lw,
86 w - 2*lw, h - 2*lw,
87
88 class Box
89 new: (@cx, @cy, @w, @h) =>
90
91 rect: =>
92 @cx, @cy, @w, @h
93
94 class Example extends UIDemo
95 click: =>
96 if @naive
97 @naive = false
98 else
99 if @show_boxes
100 @naive = true
101 @show_boxes = not @show_boxes
102
103 text: (box, text, align='center', my=.5) =>
104 mx = .1
105 @ctx.textAlign = align
106
107 if align == 'left'
108 @ctx\fillText text, box.cx + mx - box.w/2, box.cy + my
109 else if align == 'center'
110 @ctx\fillText text, box.cx, box.cy + my
111 if align == 'right'
112 @ctx\fillText text, box.cx - mx + box.w/2, box.cy + my
113
114 {
115 :UIDemo
116 :Example
117 :Box
118 }
+0
-15
root/blog/aspect_ratios/interactive/fit: text$moonscript -> fn -> mmm$component.moon less more
0 =>
1 import UIDemo from @get '_base: table'
2
3 class FitDemo extends UIDemo
4 draw: =>
5 @fit 16, 9
6 @ctx.fillStyle = 'red'
7 @ctx\fillRect -8, -4.5, 16, 9
8
9 @ctx.fillStyle = 'black'
10 @ctx.font = '6px Arial'
11 @ctx.textAlign = 'center'
12 @ctx\fillText '16:9', 0, 2
13
14 FitDemo!
+0
-34
root/blog/aspect_ratios/interactive/perforate: text$moonscript -> fn -> mmm$component.moon less more
0 =>
1 import UIDemo from @get '_base: table'
2
3 arr = (args) ->
4 with js.new js.global.Array
5 for i in *args
6 \push i
7
8 class PerforateDemo extends UIDemo
9 draw: =>
10 @fit 16, 9
11
12 @ctx.lineWidth = 0.15
13 @ctx.strokeStyle = 'green'
14 @strokeRect 0, -3.5, 16, 2
15 @strokeRect 0, 3.5, 16, 2
16
17 @ctx\setLineDash arr { 0.4 }
18 @ctx\beginPath!
19 @ctx\moveTo 0, -4.5
20 @ctx\lineTo 0, -2.5
21 @ctx\moveTo 0, 2.5
22 @ctx\lineTo 0, 4.5
23 @ctx\stroke!
24 @ctx\setLineDash arr {}
25
26 @ctx.strokeStyle = 'blue'
27 @strokeRect 0, 0, 16, 5
28
29 @ctx.font = '4px Arial'
30 @ctx.textAlign = 'center'
31 @ctx\fillText '16:5', 0, 1.5
32
33 PerforateDemo!
+0
-46
root/blog/aspect_ratios/interactive/sidebar: text$moonscript -> fn -> mmm$component.moon less more
0 =>
1 import Box, Example from @get '_base: table'
2
3 class Sidebar extends Example
4 draw: =>
5 margin_x, margin_y = @fit 16, 9
6 if @naive
7 margin_x, margin_y = 0, 0
8
9 @ctx.font = '1.5px Arial'
10 sidebar = Box -7 - margin_x, -1, 2, 7
11 @text sidebar, 'A', 'center', -1.8
12 @text sidebar, 'B', 'center', -0.3
13 @text sidebar, 'C', 'center', 1.2
14 @text sidebar, 'D', 'center', 2.7
15
16 bottom_l = Box -4 - margin_x, 3.5 + margin_y, 8, 2
17 bottom_r = Box 4 + margin_x, 3.5 + margin_y, 8, 2
18
19 @text bottom_l, 'levelname', 'left'
20 @text bottom_r, 'info a b c', 'right'
21
22 main = Box 1, -1, 14, 7
23 @ctx.lineWidth = 0.1
24 @ctx.strokeStyle = 'black'
25 @ctx\beginPath!
26 for x=-5.5, 7.5
27 @ctx\moveTo x, -4.5
28 @ctx\lineTo x, 2.5
29
30 for y=-4, 2
31 @ctx\moveTo -6, y
32 @ctx\lineTo 8, y
33 @ctx\stroke!
34
35 if @show_boxes
36 @ctx.lineWidth = 0.1
37 @ctx.strokeStyle = 'green'
38 @strokeRect sidebar\rect!
39 @strokeRect bottom_l\rect!
40 @strokeRect bottom_r\rect!
41
42 @ctx.strokeStyle = 'blue'
43 @strokeRect main\rect!
44
45 Sidebar!
+0
-28
root/blog/aspect_ratios/interactive/tear: text$moonscript -> fn -> mmm$component.moon less more
0 =>
1 import UIDemo from @get '_base: table'
2
3 arr = (args) ->
4 with js.new js.global.Array
5 for i in *args
6 \push i
7
8 class TearDemo extends UIDemo
9 draw: =>
10 margin_x, margin_y = @fit 16, 9
11
12 @ctx.lineWidth = 0.15
13 @ctx.strokeStyle = 'green'
14 @strokeRect -4 - margin_x, -3.5 - margin_y, 8, 2
15 @strokeRect 4 + margin_x, -3.5 - margin_y, 8, 2
16
17 @strokeRect -4 - margin_x, 3.5 + margin_y, 8, 2
18 @strokeRect 4 + margin_x, 3.5 + margin_y, 8, 2
19
20 @ctx.strokeStyle = 'blue'
21 @strokeRect 0, 0, 16, 5
22
23 @ctx.font = '4px Arial'
24 @ctx.textAlign = 'center'
25 @ctx\fillText '16:5', 0, 1.5
26
27 TearDemo!
+0
-55
root/blog/aspect_ratios/interactive/vtk: text$moonscript -> fn -> mmm$component.moon less more
0 =>
1 import Box, Example from @get '_base: table'
2
3 class VTK extends Example
4 draw: =>
5 margin_x, margin_y = @fit 16, 9
6 if @naive
7 margin_x, margin_y = 0, 0
8
9 levelname = Box -4 - margin_x, -3.5 - margin_y, 8, 2
10 settings = Box 4 + margin_x, -3.5 - margin_y, 8, 2
11 infobar = Box -4 - margin_x, 3.5 + margin_y, 8, 2
12 exit = Box 4 + margin_x, 3.5 + margin_y, 8, 2
13
14 main = Box 0, 0, 16, 5
15
16 @ctx.font = '1.5px Arial'
17 @text levelname, 'levelname', 'left'
18 @text infobar, 'info a b c', 'left'
19 @text settings, 'settings', 'right'
20 @text exit, 'exit', 'right'
21
22 @ctx.lineWidth = 0.2
23 @ctx.strokeStyle = 'black'
24 @ctx\beginPath!
25 @ctx\moveTo -8 - margin_x, -2.5 - margin_y
26 @ctx\lineTo 8 + margin_x, -2.5 - margin_y
27 @ctx\moveTo -8 - margin_x, 2.5 + margin_y
28 @ctx\lineTo 8 + margin_x, 2.5 + margin_y
29 @ctx\stroke!
30
31 @ctx.lineWidth = 0.1
32 @ctx.strokeStyle = 'gray'
33 @ctx\beginPath!
34 for x=-7.5, 7.5
35 @ctx\moveTo x, -2.5
36 @ctx\lineTo x, 2.5
37
38 for y=-2, 2
39 @ctx\moveTo -8, y
40 @ctx\lineTo 8, y
41 @ctx\stroke!
42
43 if @show_boxes
44 @ctx.lineWidth = 0.1
45 @ctx.strokeStyle = 'green'
46 @strokeRect levelname\rect!
47 @strokeRect settings\rect!
48 @strokeRect infobar\rect!
49 @strokeRect exit\rect!
50
51 @ctx.strokeStyle = 'blue'
52 @strokeRect main\rect!
53
54 VTK!
+0
-77
root/blog/aspect_ratios/text$markdown.md less more
0 Dealing with different screen sizes and formats can be annoying.
1 On desktop PCs, 16:9 is the most common aspect ratio these days, but depending on where you are rendering your content,
2 it might still be a different format, for example when a task bar, broswer navigation bar or similar is slicing away some of your screen real-estate.
3 For mobile games the situation is a bit worse, because there are simply more different aspect ratios out there
4 and different systems may require extra space for on-screen navigation keys, statusbars etc.
5
6 In this post I want to present a simple technique for layouting **simple UIs for games** that can work across a range of similar screens.
7 If you are looking for a way to design a UI-heavy app, or something that needs to work across very different environments (like phones, tablets and desktop PCs), this is probably not what you are looking for.
8 In any case, if you just want to take a quick look you can jump down to the [examples](#examples) at the bottom of the page as well.
9
10 ## step one: `fit`
11 The most trivial solution is to simply choose the aspect ratio you would like to work with,
12 and then fit a rectangle with that ratio into whichever space is available.
13 Depending on the screen, this may either be a perfect fit, leave space on the horizontal axis, or leave space on the vertical axis:
14 (drag the lower right corner to see this approach react to different screen sizes)
15
16 <mmm-embed path="interactive" facet="fit" nolink></mmm-embed>
17
18 This is pretty trivial to accomplish in code.
19 You can calculate the scales in relation to your reference grid on the x and y axis separately, and simply use the one with a lower value.
20 Establishing a reference grid and knowing the scale to translate between it and the physical screen sizes will also help
21 designing the layout in general, as we will see later.
22 Here is a simple example in JS:
23
24 ```js
25 // measured from somewhere
26 const width = 2000;
27 const height = 1080;
28
29 const sx = width / 16;
30 const sy = height / 9;
31
32 const scale = Math.min(sx, sy);
33 const offset_x = width * (1 - scale);
34 const offset_y = height * (1 - scale);
35
36 // the biggest 16:9 rectangle you can fit is (scale*width, scale*height) large
37 // and it's top-left corner is at (offset_x/2, offset_y/2)
38 ```
39
40 The problem with this is that it simply doesn't look very good. When the ratio is a perfect match there is of course no problem,
41 but otherwise the empty space makes the screen look empty (especially if there are system UI elements next to it).
42
43 ## step two: `perforate`
44 So how can this be improved? We would like to use the unused space on the x or y axis, but we can't just scale everything up,
45 or we would start cropping important pieces of UI. Stretching the game to fill the screen also doesn't work for obvious reasons.
46
47 To proceed, the UI has to be 'perforated' into different sections that are independent from each other.
48 This is where establishing a reference grid becomes useful to orient ourselves in the layout.
49 In my example I am splitting the screen into a main content section that is 16:5 units large, as well as a top and bottom bar.
50 The two bars are also split in half vertically in the middle, for reasons that we will see in the third step.
51
52 <mmm-embed path="interactive" facet="perforate" nolink></mmm-embed>
53
54 It's imortant to note that how you divide the screen up depends completely on your game/interface of course.
55 The layout I am using here is just an example; at the end of this post you can find another one with a different layout as well.
56
57 ## step three: `tear`
58
59 Now that the sections are defined, in the last step we can 'tear' the sections apart and decide how they should react to the
60 left-over space calculated in step one individiually.
61 In my layout, I stretch the top and bottom bars to fill the screen completely horizontally.
62 By dividing the bars into separate sections in the last step, I know how much space is guaranteed to be available on each side.
63 If there is room left on the vertical axis, I move the bars out from the center to give the content some visual space:
64
65 <mmm-embed path="interactive" facet="tear" nolink></mmm-embed>
66
67 Once again, how you make the pieces behave depends a lot on what elements your UI has in the first place, and how you want it to look and feel.
68
69 # examples
70
71 Finally, here are two examples with a bit more visual coherence to show how this actually ends up working.
72 You can click on these to cycle between the normal view, showing the frames used to subdivide the canvas, and viewing `fit` only for comparison.
73
74 <mmm-embed path="interactive" facet="vtk" nolink></mmm-embed>
75 <mmm-embed path="interactive" facet="sidebar" nolink></mmm-embed>
76
+0
-1
root/blog/aspect_ratios/title: text$plain less more
0 Aspect-ratio independent UIs
+0
-15
root/blog/automating_my_rice/$order less more
0 sexy
1 dark
2 bwcube
3 tattooed
4 polysun
5 hotline
6 sidewalk
7 akira
8 laying
9 touching
10 twostripe
11 trippy
12 polar
13 cavetree
14 psych
root/blog/automating_my_rice/akira/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/bwcube/image$png.png less more
Binary diff not shown
+0
-1
root/blog/automating_my_rice/categories: text$plain less more
0 update ricing linux themer
root/blog/automating_my_rice/cavetree/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/dark/image$png.png less more
Binary diff not shown
+0
-1
root/blog/automating_my_rice/date: time$iso8601-date less more
0 2015-08-08
root/blog/automating_my_rice/hotline/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/laying/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/polar/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/polysun/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/psych/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/sexy/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/sidewalk/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/tattooed/image$png.png less more
Binary diff not shown
+0
-46
root/blog/automating_my_rice/text$markdown.md less more
0 I spent the bigger part of today writing a small script that cylces through all my [Themer][themer] themes,
1 opens a dmenu, prints the theme name with figlet and shows screenfetch before taking a picture.
2 The script is pretty straightforward:
3
4 #!/usr/bin/bash
5
6 for theme in $(themer list); do
7 themer activate $theme
8 sleep 20 # wait for bar :/
9 dmenu -p "Launch:" $(~/.i3/dmenuconf) < ~/.cache/dmenu_run &
10 dmenupid=$!
11 clear
12 screenfetch
13 echo
14 toilet --gay $theme
15 echo;echo;echo;echo;echo;echo;echo
16 themer current
17
18 sleep 1
19 scrot $theme.png
20 kill $dmenupid
21 done
22
23 So here are all my current themes:
24
25 <mmm-embed path="cavetree"></mmm-embed>
26 <mmm-embed path="akira"></mmm-embed>
27 <mmm-embed path="sidewalk"></mmm-embed>
28 <mmm-embed path="hotline"></mmm-embed>
29 <mmm-embed path="polar"></mmm-embed>
30 <mmm-embed path="polysun"></mmm-embed>
31 <mmm-embed path="psych"></mmm-embed>
32 <mmm-embed path="trippy"></mmm-embed>
33 <mmm-embed path="twostripe"></mmm-embed>
34 <mmm-embed path="bwcube"></mmm-embed>
35
36 The wallpaper for the last one is intended to be tiled, not stretched, but that currently requries a manual change in my i3 config:
37 <mmm-embed path="dark"></mmm-embed>
38
39 I am thinking about implementing this as a Themer feature, but it would require it's own *presentation* plugin type,
40 so everyone can choose their own commands, bars, and waiting time.
41
42 You can find more information about [Themer on the github page][themer], along with all my [config files][dotfiles].
43
44 [themer]: https://github.com/s-ol/themer
45 [dotfiles]: https://github.com/s-ol/dotfiles
+0
-1
root/blog/automating_my_rice/title: text$plain less more
0 Automating my Rice
root/blog/automating_my_rice/touching/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/trippy/image$png.png less more
Binary diff not shown
root/blog/automating_my_rice/twostripe/image$png.png less more
Binary diff not shown
+0
-1
root/blog/challenging_myself/categories: text$plain less more
0 update
+0
-1
root/blog/challenging_myself/date: time$iso8601-date less more
0 2015-06-27
+0
-25
root/blog/challenging_myself/text$markdown.md less more
0 # Yay, a first post! A blank blog!
1
2 I am starting to blog now as part of an... experiment of sorts.
3 I have a lot of time on my hands at the moment and I choose to spend it all boring myself.
4 I re-discovered [streak.club][streak.club] a few days ago and so I decided to give it a shot and try a few new things.
5
6 If you never heard of **streak.club**, you should definetely check it out.
7 It is a small social network built by [leafo](https://twitter.com/moonscript) where people can get together and participate in *Streaks*.
8 Once you join a streak, you submit an entry every day or every week (depending on the Streak's settings).
9 People can then like and comment on your entries and give you feedback for your creations.
10 If you miss a day, nothing bad happens, but yout streak counter gets reset.
11
12 One of the streaks I joined is the [*Blog every week*][blogeweek] streak, which I will submit this first post to when I am done.
13 I also created my own streak, [*Daily Stencil Art*][stencils]. So, for the next month, I will try to design and/or paint one stencil per day.
14 The [first stencil](https://streak.club/p/7129/kill-bill-the-bride-stencil-by-s0lll0s) I did for the streak turned out really nice, so I will continue to design some in that art style (gold background with black shadow pieces on top and a small piece in an accent color).
15
16 As for the future of this blog, I am not quite sure where this will go.
17 I think I am going to cover my stencil creation process in a post soon (maybe today, maybe next week, who knows?) and also blog about programming/development of games and other stuff I might write.
18 Maybe I will also port some of the tutorials I have written so far to markdown and put them here aswell.
19
20 I hope that **streak.club** will work as well long-term as it does right now, motivation is still probably my number one enemy and streak.club is an excellent way for me to focus on new things and get away from the computer some more aswell.
21
22 [streak.club]: https://streak.club
23 [stencils]: https://streak.club/s/614/daily-stencil-art
24 [blogeweek]: https://streak.club/s/103/blog-every-week
+0
-1
root/blog/challenging_myself/title: text$plain less more
0 Challenging myself
+0
-6
root/blog/clocks_triggers_gates/$order less more
0 tyktok_test
1 tyktok_leds
2 tyktok_tired
3 metronome
4 tyktok_tomcat
5 threshold
+0
-1
root/blog/clocks_triggers_gates/categories: text$plain less more
0 update gamedev electronics
+0
-1
root/blog/clocks_triggers_gates/date: time$iso8601-date less more
0 2017-06-28
root/blog/clocks_triggers_gates/metronome/image$jpeg.jpg less more
Binary diff not shown
+0
-137
root/blog/clocks_triggers_gates/text$markdown.md less more
0 A classic module in modular synthesisers is a sequencer. It allows you to create rhythms and
1 melodies by letting you set a sequence of values to output over some time.
2 For example a drum machine usually contains a step-sequencer where for every *step* in the
3 *sequence* you can select whether to play, or not play, a drum.
4
5 *CV Sequencers* haver a dial in place of that on/off switch, where you can set a value for the
6 *Control_Voltage* to be output.
7 That signal can then be fed into another module to do something interesting, like control the
8 pitch of an oscillator, the cut-off point of a filter or the gain of an amplifier.
9
10 Sequencers usually have an internal clock, with a dial to set the speed, but also an external clock
11 input, where you can feed in a square wave signal.
12 Every time the square wave goes from low (0V) to high (usually anything over 5V) the sequencer steps
13 to the next beat.
14
15 Since we have a multitude of sound-generators that we want to use in interesing rhythms, multiple
16 sequencers sound natural and a central clock could certainly help.
17
18 ## hit it
19 However a digital, steady clock is pretty boring, so I came up with an idea.
20 I remembered the old wooden metronome that my grandma had at her piano and how I used to play with
21 it as a child. If we had a real metronome tick away, we could hold the needle in place,
22 tap it to add an extra beat where there wasn't supposed to be one and more.
23
24 <mmm-embed path="metronome"></mmm-embed>
25
26 Analog metronomes like this are actually pretty interesting little devices.
27 They are purely mechanical and contain a spring that you have to wind up to give it power.
28 On the front there is a needle with a weight at the top and the bottom.
29 By moving the top weight up and down the speed of the needle,
30 and thereby the ticking-rate can be set.
31 Every time the needle centers (like it is on the image below), a mechanism in the back makes a small
32 metal pin slam against a metal plate glued to the case, making a loud clicking noise.
33
34 With this idea, we were on the lookout for a metronome on our trip to the fleamarket
35 but couldn't find one, so I ended up buying the tiny one above (the size is pretty nice anyway).
36
37 The idea was (and it worked out that way!) to pick up the clicks somehow and use them to trigger
38 sequencers.
39 Originally I thought about putting a light dependant resistor (LDR) somewhere under the
40 counterweight, and measure the shadow as a signal.
41 In the time until I got the Metronome, I discussed this with Sam Battle, who runs the awesome
42 youtube channel [LOOK MUM NO COMPUTER][no-computer], and he proposed to use a piezo crystal
43 (sometimes referred to as *Contact Mic*) instead.
44
45 As it turns out, the metronome has plenty of space to glue the piezo right next to the metal plate
46 that the metronome hits with the metal pin, where it can pick up all that very nicely.
47 The 1/4" (6.3mm) audio jack also fit very well behind the panel, where there is a rather large
48 empty compartment, I guess to amplifiy the ticking sound.
49 I just drilled a hole in the back, screwed the jack in and soldered two wires; done!
50
51 <mmm-embed path="tyktok_test" nolink></mmm-embed>
52
53 In this first test you can see the Piezo work as a microphone amplifying the ticking noise on my
54 speakers.
55
56 ## signal shapes
57 That was a first success, but the signal we get from the metronome is not very clean.
58 Not only does it pick up random noises from the environment (sometimes intentionally),
59 the click is no square wave since it resonates through the plastic material.
60
61 To clean up the signal initially I took a look on it on the Oscilloscope.
62 It looks pretty much like what you would expect, swinging up and down a few times,
63 each with a drastic drop in amplitude.
64
65 The first attempt was to just use a comparator.
66 The output from the metronome goes to the non-inverting-input, meaning that when it is higher than
67 the other (inverting) input, the output of the comparator swings high.
68 This is what I wanted since sequencers trigger on the rising edge.
69 The inverting input is wired to a potentiometers center pin, with the other two pins at the supply
70 voltage and ground (0 and 9V) to form a voltage divider that lets me tune the threshold to any
71 any voltage in that range.
72 The output signal is now what you would call a *Trigger* signal; it is usually low and sometimes
73 jumps up to 9V for a *very short* duration (while the tick clips over the threshold on it's first
74 and loudest swing). I didn't measure the pulse length, but it's probably under 10ms long.
75
76 To test the circuit, I fed the output to a (CD)4017 counter.
77 This IC can be used to build sequencers or clock dividers rather easily (more on that later).
78 I just set it up to light up 4 LEDs in sequence, so that I could see whether the circuit was fine.
79 After an hour or so of figuring out stupid wiring mistakes and learning about open-drain outputs
80 it was working:
81
82 <mmm-embed path="tyktok_leds" nolink></mmm-embed>
83 (this tweet is mislabeled, Juan's sequencer is in the next video).
84
85 [Juan][juan] had an Akai Tomcat drum machine on hand, and we were stoked to try it out.
86 As usual at first, it didn't work at all.
87 Pretty soon we figured out that the signal was just too quiet coming off our 9V supply,
88 so we put an amplifier in between (alongside a whole mess of cables) and behold:
89
90 <mmm-embed path="tyktok_tomcat" nolink></mmm-embed>
91
92 This worked pretty well, surprisingly!
93 With the potentiometer set *just right*, it rarely triggered twice, and ran for some time before the
94 spring weakened and the sound became a tiny bit more quiet and failed to trigger every beat.
95 This is also a really nice effect actually and transforms rather simple beats into nice wacky ones:
96
97 <mmm-embed path="tyktok_tired" nolink></mmm-embed>
98
99 The schematic at this point is very simple:
100
101 <mmm-embed path="threshold"></mmm-embed>
102
103 I used an LM393 dual comparator and tied the unused half to ground, as the datasheet recommends.
104
105 ## dividing time
106 While this was working very well already, I had an idea for improving it using the universal 555
107 timer IC. However, I didn't have any 555s on hand (or rather, I wasn't aware [Ludonaut][ludonaut]
108 had a 556 in his box next to me) so I kept that in mind but went on with other things
109 (more on this in the upcoming post).
110
111 Reading up on synthesizer modules, I found *Clock Dividers* rather often. In the beginning I wasn't
112 quite sure what they were, but it turns out they are pretty simple: basically they just slow down a
113 clock (signal) by counting beats and only producing one of their own every N beats in the incoming
114 signal. I thought this would be very useful for us, since using clock dividers with non-multiple
115 divisons such as /3, /4 and /5 can create nice polyrhythms.
116
117 My plan is to build this right into the same box as the clock threshold circuitry.
118 In the videos above you can already see it working with /2, /3, /4 and /6 outputs.
119 The schematic is largely copied from [Ken Stone's amazing synthesizer project, CGS][cgs].
120 He has a nice collection of synth module circuits, and here I am using the pulse divider part of the
121 [CGS36 Pulse Divider and Boolean Logic][cgs36] module:
122
123 [![CGS36 Pulse Divider][cgs36-schematic.gif]][cgs36]
124
125 For now I left out the /7, /8 and /5 parts, but I think the /5 would be useful since it introduces
126 another prime division that is not much faster or slower than the /3 and /4 we already have.
127 The /8 would be almost 'for free' since it doesn't require a new 4017, but I'm not sure whether I
128 should put it in with the /7 missing. I guess it could be useful for a slow melody sequencer.
129
130 [no-computer]: https://www.youtube.com/watch?v=fO1nbHoEZMw
131 [juan]: https://twitter.com/juanorloz
132 [ludonaut]: https://twitter.com/ludonaut
133 [cgs]: http://www.cgs.synth.net/modules/
134 [cgs36]: http://www.elby-designs.com/webtek/cgs/cgs36/cgs36_pulse_divider.html
135
136 [cgs36-schematic.gif]: http://www.elby-designs.com/webtek/cgs/cgs36/schem_cgs36v14_pulse_divider.gif
root/blog/clocks_triggers_gates/threshold/image$jpeg.jpg less more
Binary diff not shown
+0
-1
root/blog/clocks_triggers_gates/title: text$plain less more
0 Clicks, Clocks and Triggers
+0
-1
root/blog/clocks_triggers_gates/tyktok_leds/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/879046250880008193
+0
-1
root/blog/clocks_triggers_gates/tyktok_test/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/878328052744241152
+0
-1
root/blog/clocks_triggers_gates/tyktok_tired/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/879072879924711426
+0
-1
root/blog/clocks_triggers_gates/tyktok_tomcat/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/879061152520699904
+0
-1
root/blog/humane_filesystems/categories: text$plain less more
0 design paper
+0
-1
root/blog/humane_filesystems/date: time$iso8601-date less more
0 2018-02-28
+0
-1
root/blog/humane_filesystems/hidden: text$lua -> bool.lua less more
0 return true
+0
-73
root/blog/humane_filesystems/text$markdown.md less more
0 Lately I've been thinking about note-taking tools.
1
2 I use a few different systems for taking notes almost daily:
3 - multiple paper notebooks and sheets of paper as available
4 - random freeform text files spread around my (various) PCs
5 - google keep
6
7 I like taking paper notes because it's easy to switch to doodle and sketch and layout everything together.
8 I also find that it stimulates my thinking a bit more that typing text; I'm not quite sure why that is.
9
10 Anyway, I was looking for solutions to replace google keep, mostly because I like to self-host my services.
11
12 ## file system use cases
13 - retrieve a file i know exists
14 - either file i know 100%
15 - or looking for a file based on content, guessing where and how it might be
16 - show related files to what i am looking for
17 - allow exploring content
18
19 ### tags and search don't solve anything
20 Many proposals in [FileSystemAlternatives][c2-fsalt] and [LimitsOfHierarchies][c2-loh] are to generalize up from Hierarchies to Set Theory.
21 This in effect means exchanging the any-to-one association between 'containers' (folders/tags) and 'contents' (files) for a any-to-any association.
22 Files are then no longer identified by their chain of parents, but rather by their belonging and not-belonging to all the existant tags.
23 Generally the proposal is to query these tag-based filesystems using combinatory logic such as `a AND (b OR NOT(C))` and so on.
24
25 My main problem with this approach is that sooner or later you end up with a huge list of tags that is just unmanageable.
26 It's obvious that this won't scale as the amount of tag increases with the amount of different data,
27 especially in a collaborative, professional or otherwise networked context.
28
29 Since the tags are all stored flatly in a bag of stuff, they also cannot meaningfully express relationships between each other.
30 This is clearly evident in any software that uses tags as the only option of organizing large amounts of data,
31 especially when multiple users have access.
32 Here is an example of a typical list of defined tags for issue tracking on github:
33
34 ![github tags][github-tags]
35
36 As can be seen easily, users are shoehorning more information into the label name string than the label is made to store.
37 As this extra data is only available to humans interpreting the text, it cannot be used to create good UI:
38 Instead of seeing a drop-down for *Tech Complexity*, users need to go through the label list.
39 Even the simplest logical constraints cannot be documented and enforced with this system.
40
41 -> need for *dynamically changeable* data schemas
42
43 /*
44 But even organizing and finding files with this paradigm is not a very nice experience; take these UI designs for example:
45
46 ![set picker from tablizer][tablizer-tagging]
47 ![search dialog from tablizer][tablizer-search]
48
49 Granted, it's not particularily fair to take these designs from 2002, but this paradigm is all around us today and the UX hasn't gotten a lot better.
50 Here's the same set picker functionality as seen on google keep, today:
51
52 ![labelling UI in google keep][keep-label]
53
54 And for the search, they didn't even try. There's just a list
55
56 A very thorough proposal for a file system can be found in [*A Novel, Tag-Based File System*][tag-based-fs].
57
58 This approach
59 */
60
61 ### is there more?
62 - the file system as a note-taking app
63
64 [tiddlywiki]: https://tiddlywiki.com/
65 [tablizer-search]: http://www.reocities.com/tablizer/setscrn1.gif
66 [tablizer-tagging]: http://www.reocities.com/tablizer/setpicker.gif
67 [tablizer-src]: http://www.reocities.com/tablizer/sets1.htm
68 [tag-based-fs]: http://digitalcommons.macalester.edu/cgi/viewcontent.cgi?article=1036&context=mathcs_honors
69 [finder-column-view]: http://cdn.osxdaily.com/wp-content/uploads/2010/03/set-column-view-size-default-mac-os-x-finder-610x307.jpg
70 [finder-column-view-src]: http://osxdaily.com/2010/03/25/setting-the-default-column-size-in-mac-os-x-finder-windows/
71 [finder-list-view]: http://cdn.osxdaily.com/wp-content/uploads/2014/12/messages-attachments-folder-mac-osx.jpg
72 [finder-list-view-src]: http://osxdaily.com/2014/12/03/access-attachments-messages-mac-os-x/
+0
-1
root/blog/humane_filesystems/title: text$plain less more
0 Notetaking and humane Filesystems
+0
-4
root/blog/love_lua_photoshop_and_games/$order less more
0 final
1 animating
2 sheet
3 outside
root/blog/love_lua_photoshop_and_games/animating/video$mp4.mp4 less more
Binary diff not shown
+0
-1
root/blog/love_lua_photoshop_and_games/categories: text$plain less more
0 update gamedev engine programming
+0
-1
root/blog/love_lua_photoshop_and_games/date: time$iso8601-date less more
0 2016-05-29
root/blog/love_lua_photoshop_and_games/final/video$mp4.mp4 less more
Binary diff not shown
root/blog/love_lua_photoshop_and_games/outside/image$jpeg.jpg less more
Binary diff not shown
root/blog/love_lua_photoshop_and_games/sheet/video$mp4.mp4 less more
Binary diff not shown
+0
-334
root/blog/love_lua_photoshop_and_games/text$markdown.md less more
0 Recently I've been building a 2d game engine for my semester project at [CGL](http://colognegamelab.de).
1 Below, I'll copy and paste two blog posts from my internal documentation blog:
2
3 ---
4
5 After nailing down the basic narrative and gameplay idea in a lengthy group discussion on the first day,
6 we each set goals for the next few days in order to kickstart the project.
7
8 As we decided on a pixel art art approach and point-and-click gameplay, as the programmer I decided to write a custom engine in
9 [LÖVE](https://love2d.org) instead of using a bulky engine like Unity that we wouldn't really profit from anyway and that would be less specific,
10 and therefore less fitting, than what I could come up with.
11 Following this, my goal as an engine programmer was then to reduce any effort needed to put content into the engine to a minimum.
12 I remembered iteration speeds and how most things I did as a programmer last time I built a pixel art project in Unity really weren't programming but replacing assets; adding states in an animation state machine that I could've easily coded by hand in a fraction of the time; and generally wrestling with the development environment, and I wanted to instead create a working environment that would make my job as the gameplay programmer and the job of the artist(s) a lot easier.
13
14 Because creating my own engine is not a minor task and obviously a very crucial one for the project,
15 the decision to go ahead and not use a prebuilt Engine was to be thought out thoroughly,
16 if I am to write my own engine it needs to work _at least_ as good as a prebuilt one or it will harm the project.
17 To test whether I could actually improve workflow and build something my team can profit from, without degrading the project work during the time I need to actually put it together, I set myself a three-day deadline to try and go as far as I can and see whether writing the engine myself seemed realistic.
18 The other goal of this trial phase was to show my team members why the switch could be worthwhile for them also.
19
20 Therefore my main starting point was to build a feature that they would see as useful and that was central to what the engine was going to do later: **rendering and scene management.**
21
22 Specifically, I started writing something that loads a .PSD directly and animates the sublayers.
23 I also added code that detects file changes and reloads them automatically.
24 At the end of the second project day, I had this:
25
26 <mmm-embed path="sheet" nolink></mmm-embed>
27
28 The code for this isn't much, but it took a few iterations to make the file- reload-watching reuseable and work on Linux and Windows alike (and efficient).
29 I used a 3rd party library for loading the photoshop files, but I had to patch it a lot.
30 Here's the main part for the animated-layer-loading:
31
32 artal = require "lib.artal.artal"
33
34 ALL_SHEETS = setmetatable {}, __mode: 'v'
35 RECHECK = 0.3
36
37 class PSDSheet
38 new: (@filename, @frametime=.1) =>
39 @time = 0
40 @frame = 1
41
42 @reload!
43
44 if WATCHER
45 WATCHER\register @filename, @
46
47 reload: =>
48 print "reloading #{@filename}..."
49
50
51 @frames = {}
52 target = @frames
53 local group
54
55 psd = artal.newPSD @filename
56 for layer in *psd
57 if layer.type == "open"
58 if not group
59 layer.image = love.graphics.newCanvas psd.width, psd.height
60 love.graphics.setCanvas layer.image
61 table.insert target, layer
62 group = layer.name
63 elseif layer.type == "close"
64 if layer.name == group
65 love.graphics.setCanvas!
66 group = nil
67 else
68 if not group
69 table.insert target, layer
70 else
71 love.graphics.draw layer.image, -layer.ox, -layer.oy if layer.image
72
73 update: (dt) =>
74 @time += dt
75
76 @frame = 1 + (math.floor(@time/@frametime) % #@frames)
77
78 draw: (x, y, rot) =>
79 {:image, :ox, :oy} = @frames[@frame]
80 love.graphics.draw image, x, y, rot, nil, nil, ox, oy if image
81
82 {
83 :PSDSheet,
84 }
85
86
87 ---
88
89 After being able to load simple animations from photoshop without even closing the game was working, I tried loading the scene İlke had created meanwhile:
90
91 <mmm-embed path="outside"></mmm-embed>
92
93 Naturally, this failed at first.
94 He was using clipping masks and blend modes, neither of which were implemented at the time,
95 but after I made a few changes to hide the affected layers for the time being, it looked fine.
96
97 My overall engine goal was to be able to build something like 90% of the level right in photoshop - including animations, hit areas, player spawns etc.
98 Basically I wanted to use Photoshop as a full level editor and only write gameplay scripts and engine code outside of it.
99
100 This meant that there would be very different types of information inside a level .psd that we would need to be accessible to the engine.
101
102 To load information and behaviours into the level structure that is loaded from the PSD I created a layer naming conventions;
103 layers can specify a _Directive_ afte their name.
104
105 The most important Directive (so far) is _load_: it loads a lua/moonscript _mixin_ via it's name and passes arguments to it .
106 For example there is a 'common' (shared between different scenes/levels) module called _subanim_ that treat's a group inside a bigger,
107 non-animated photoshop document as an animation (like what I did in the first post, but inside a complex scene).
108 The _subanim _module has one parameter, the frame-duration (in seconds).
109 To turn a group into a _subanim_, you have to append 'load:subanim,0.2' to it's name for example.
110
111 Another Directive is _tag_, it stores the layer under a name so other scripts can access it specifically and consistently.
112 Because this could also be done by a mixin, i am thinking about abolishing the _Directive_ concept and only using mixins (so that the _load_ could be removed also).
113 You can see the system working in this clip:
114
115
116 <mmm-embed path="animating" nolink></mmm-embed>
117 _making an animation out of the single rain layer_
118
119 Moreover, I added a directory structure for mixins; to load a mixin called _name _for a scene called _scene_, it first looks in the scene specific directories:
120
121 - game/_scene_/_name_.moon
122 - game/_scene.moon_ (this file can return multiple mixins)
123
124 if neither of these exist, it checks for _common_ mixins of this name in the common directory and files:
125
126 - games/common/_name.moon_
127 - games/common.moon (this file can return multiple mixins)
128
129 This allows to share mixins between scenes (like click-area mixins maybe, or the _subanim_ mentioned above)
130 but still keep a clean directory structure for specific elements (like the dialogue of a certain scene,
131 or the tram in the background of the scene we are working on currently).
132
133 Mixin code can modify the scene node / layer object and overwrite the default "draw" and "update" hooks/methods.
134 This allows for nearly everything I can think of right now, but most mixins are still very short and concise.
135 As examples, you can take a look at the code that animates the tram in the background or the subanim source:
136
137 game/first_encounter/tram.moon:
138
139 import wrapping_, Mixin from require "util"
140
141 wrapping_ class SubAnim extends Mixin
142 SPEED = 440
143 new: (scene) =>
144 super!
145
146 @pos = 0
147
148 update: (dt) =>
149 @pos = (@pos + SPEED*dt) % (WIDTH*2)
150
151 draw: (recursive_draw) =>
152 love.graphics.draw @image, @pos/4 - @ox - 140, -@oy
153
154
155 game/common/subanim.moon:
156
157 import wrapping_, Mixin from require "util"
158
159 wrapping_ class SubAnim extends Mixin
160 new: (scene, @frametime=0.1) =>
161 super!
162
163 @time = 0
164 @frame = 1
165
166 update: (dt) =>
167 @time += dt
168
169 @frame = 1 + (math.floor(@time/@frametime) % #@)
170
171 draw: (recursive_draw) =>
172 recursive_draw {@[@frame]}
173
174
175 _wrapping_ is a small helper that allows a moonscript class to wrap an existing lua table
176 (= object, in this case the layer objects produced by the psd parsing phase) and Mixin is a class that handles mixin live-reloading
177 (yep, that works with mixins too!) and might contain utility functions to write better mixins in the future.
178 Here's the code for both:
179
180 wrapping_ = (klass) ->
181 getmetatable(klass).__call = (cls, self, ...) ->
182 setmetatable self, cls.__base
183 cls.__init self, ...
184
185 klass
186
187 class Mixin
188 new: =>
189 info = debug.getinfo 2
190 file = string.match info.source, "@%.?[/\\]?(.*)"
191
192 @module = info.source\match "@%.?[/\\]?(.*)%.%a+"
193 @module = @module\gsub "/", "."
194
195 if WATCHER
196 WATCHER\register file, @
197
198 reload: (filename) =>
199 print "reloading #{@module}..."
200
201 package.loaded[@module] = nil
202 new = require @module
203
204 setmetatable @, new.__base
205
206 find_tag: =>
207 layer = @
208 while not layer.tag
209 layer = layer.parent
210
211 if not layer
212 return nil
213
214 layer.tag
215
216 {
217 :wrapping_,
218 :Mixin
219 }
220
221
222 By the end of the three day "test phase".
223 This is how the first scene looked in-game:
224
225 <mmm-embed path="final" nolink></mmm-embed>
226
227 Here's _psdscene.moon_, wrapping most things mentioned in this article:
228
229 artal = require "lib.artal.artal"
230
231 class PSDScene
232 new: (@scene) =>
233 @reload!
234
235 if WATCHER
236 WATCHER\register "assets/#{@scene}.psd", @
237
238 load: (name, ...) =>
239 _, mixin = pcall require, "game.#{@scene}.#{name}"
240 return mixin if _ and mixin
241
242 _, module = pcall require, "game.#{scene}"
243 return module[name] if _ and module[name]
244
245 _, mixin = pcall require, "game.common.#{name}"
246 return mixin if _ and mixin
247
248 _, module = pcall require, "game.common"
249 return module[name] if _ and module[name]
250
251 LOG_ERROR "couldn't find mixin '#{name}' for scene '#{@scene}'"
252 nil
253
254 reload: (filename) =>
255 filename = "assets/#{@scene}.psd" unless filename
256 print "reloading scene #{filename}..."
257
258 @tree, @tags = {}, {}
259 target = @tree
260 local group
261
262 indent = 0
263
264 psd = artal.newPSD filename
265 for layer in *psd
266 if layer.type == "open"
267 table.insert target, layer
268 layer.parent = target
269 target = layer
270 LOG "+ #{layer.name}", indent
271 indent += 1
272 continue -- skip until close
273 elseif layer.type == "close"
274 layer = target
275 target = target.parent
276 indent -= 1
277 else
278 LOG "- #{layer.name}", indent
279 table.insert target, layer
280
281 cmd, params = layer.name\match "([^: ]+):(.+)"
282 switch cmd
283 when nil
284 ""
285 when "tag"
286 @tags[params] = tag
287 layer.tag = params
288 when "load"
289 params = [str for str in params\gmatch "[^,]+"]
290 name = table.remove params, 1
291
292 mixin = @load name
293 if mixin
294 LOG "loading mixin '#{@scene}/#{name}' (#{table.concat params, ", "})", indent
295 mixin layer, unpack params
296 else
297 LOG_ERROR "couln't find mixin for '#{@scene}/#{name}'", indent
298 else
299 LOG_ERROR "unknown cmd '#{cmd}' for layer '#{layer.name}'", indent
300
301 update: (dt, group=@tree) =>
302 if group == false
303 return
304
305 for layer in *group
306 if layer.update
307 layer\update dt, @\update
308 elseif layer.type == "open"
309 @update dt, layer
310
311 draw: (group=@tree) =>
312 if group == false
313 return
314 elseif group == @tree
315 love.graphics.scale 4
316
317 for layer in *group
318 if layer.draw
319 layer\draw @\draw
320 elseif layer.image
321 {:image, :ox, :oy} = layer
322 love.graphics.setColor 255, 255, 255, layer.opacity or 255
323 love.graphics.draw image, x, y, nil, nil, nil, ox, oy
324 elseif layer.type == "open"
325 @draw layer
326
327 {
328 :PSDScene,
329 }
330
331
332 Seeing that everything was (and is) going very smoothly up to this point,
333 I decided to "end" the test phase and finalize the decision to roll out my own engine.
+0
-1
root/blog/love_lua_photoshop_and_games/title: text$plain less more
0 LÖVE, Lua, Photoshop + Games
+0
-2
root/blog/ludum_dare_33_postmortem/$order less more
0 smert
1 split
+0
-1
root/blog/ludum_dare_33_postmortem/categories: text$plain less more
0 update gamedev gamejam
+0
-1
root/blog/ludum_dare_33_postmortem/date: time$iso8601-date less more
0 2015-08-25
root/blog/ludum_dare_33_postmortem/smert/image$png.png less more
Binary diff not shown
root/blog/ludum_dare_33_postmortem/split/image$png.png less more
Binary diff not shown
+0
-93
root/blog/ludum_dare_33_postmortem/text$markdown.md less more
0 So, LD33 is over, and once again I made something: [*The Monster Within*][entry].
1
2 # Theme
3 The theme was *You are the Monster*, and I wanted to really incorporate the theme into the gameplay mechanics for once
4 (well yeah, [Curved Curse][curcur] did that, but the topic was very broad and there was no way but incorporate it into the gameplay itself).
5
6 Many games I have seen took the theme quite literally and made the main character a monster:
7
8 <mmm-embed path="smert"></mmm-embed>
9 [*image by @RedOshal*][smert_src]
10
11 Although you play a monster in *The Monster Within*, I took the theme a bit further.
12 The theme reminded me of an exclamated "*Look at yourself! You've become a Monster!*", something a movie wife might say to her increasingly out-of-control husband who just killed the neighbor that found out about the family's dark secret.
13
14 The idea I came up with was the following: your main objective is to eliminate enemies from within a crowd of mostly civilians (in a generic top-down action game). However you turn into a Monster whenever you kill someone, which grants you a lot more power, but also impairs your senses; you can no longer differentiate between civilians and enemies.
15 As a result, I figured, people would have to decide between two playstyles, going on a rampage and killing as many people as possible, regardless of their type, or trying to play strategically by memorizing the enemies in the crowd.
16
17 <mmm-embed path="split"></mmm-embed>
18 *The Monster Within, normal and beast mode*
19
20 To make playing in "Beast Mode" more appealing I increased the primary attack (punch) range and added a secondary lunge attack that is exclusive to that mode. This, I hope, tempts players to make use of the beast mode and facilitates players stopping to play for the original objective - eliminating enemies - and instead try to kill as many people as possible (which is an intended effect).
21
22 To emphasize the two approaches, I introduced a dual scoring system; there is a "good" score that you can increase by eliminating enemies that is penalized whenever you kill a civilian but there is also an "evil" score that increases by the same amount regardless of the type of character you killed.
23 To balance the two, killing enemies yields more "good" points than "bad" ones, so both options can be viable.
24
25 As the current comments on the [ludum dare entry page][entry] show, not everyone understood what I was trying to achieve:
26
27 >It was fun although I didn't really like the mechanic where everyone turns into ghosts; it requires some ridiculous amount of memorization in order to get a good score, so I just ran around punching everyone indiscriminately.
28
29 Although it seems *charliecarlo* was completely oblivious to my actual intentions with the core mechanic, which always feels a bit bad, he perfectly demonstrated that my idea worked out; his character let the *monster within* loose and went on a bloody rampage.
30
31 >Different between beast and human score was initially confusing. Then again, that fits the theme well, a black box morality system.
32
33 >I like the concept, the art is nice and I killed 50 innocent souls! - Cool game!
34
35 So overall I am happy with how the mechanic turned out and was received; the three weeks of rating will hopefully yield more critique and comments.
36
37 # Techology
38
39 ## Moonscript
40 Although I have been working on a networked game engine on top of [LÖVE][love] in moonscript, this is the first **complete** game that I write in [moonscript][moonscript].
41 Writing aforementioned engine definetely helped me form some habits that sped up development for this game.
42 *The Monster Within* turned out to be an amazingly small game at 768 lines of code (excluding libraries); [Curved Curse][curcur] counted 1160.
43
44 Every time I had to read or write Lua code (for example my older library, `st8`, which still has hiccups that I needed to iron out) the syntax seemed extremely cumbersome and restrictive.
45 After over a month, moonscript is still fun to write and read and I the decision to give it a shot was definetely more than worth it. Thank you leafo!
46
47 ## Steering Behaviors
48 A few months back I stumbled upon the [very nice series *Understanding Steering Behaviors*][steering] on *tutsplus.com*.
49 I used those guides, alongside the sixth chapter of *Daniel Shiffman*'s *The Nature of Code*, [*Autonomous Agents*][autonom], to implement the character AI for the enemies and civilians.
50
51 In particular, I used the `wander`, `flee` (from the player, when he is in beast mode), `collision avoidance` and the `seperation` behaviors to make the characters move around in a more or less natural and pleasant-looking fashion.
52
53 The implementation consists of just a few lines of vector math and the results are surprisingly lifelike for the very little effort I had to put into it.
54
55 ## Box2D
56 I was very unsure whether I should roll out my own simple physics system with a little Vector math, or whether I should use Box2d (which ships with LÖVE anyway).
57 I opted to choose `Box2D` because that would enable things like destructable environments (like houses being knocked away) and novel interactions in more carefully designed environments later down the road.
58
59 ## Optimization
60 I didn't really optimize anything, and if I didn't know it doesn't matter at the current level scale, I would have long added a spatial hash system, stopped simulating characters that are long out of view or at the very least culled the map.
61 However I worked so slowly on the first two days that there really wasn't any time left for that sort of thing, and most of the game only started working on the last day so there wasn't a lot of optimization possible before that point anyway.
62 If I continue working on this project, Optimization is one of the first things I will deal with.
63
64 # Productivity
65 This Ludum Dare hit me entirely unprepared, I had completely forgotten about the date and only noticed a day prior.
66 I was initially very unsure whether to participate at all and also couldn't reach my artist from last year's game.
67 I put out a tweet and a post to [r/gamedev][rgamedev] and `stewartisme` contacted me as I was sleeping after looking at the theme at 3AM.
68 Still, I wasn't very motivated and worked slowly, procrastinated a lot and overall didn't really 'get into it'.
69 It amazes me that the game even turned out playable and with an acceptable look in general, but on the last day we really did work until the last minute, and as usual 90% of the perceived complete-ness were achieved in the last 10% of the time.
70
71 # Future?
72 I'm not sure whether this project will continue, but I have a few ideas on how to improve the game.
73 In particular, I would like to add a Highscores table.
74 Because every *The Monster Within* run yields two scores, there are multiple options for this.
75 Aside from the obvious solution of two seperate high score tables, the most interesting option from a game design perspective, would be to have a single table, and to enter whichever score is higher there.
76 The entries could be colored white and red, denoting whether the player followed the "good" objective or let himself get carried away.
77 It would probably be required to fine tune the scoring system so that both playstyles are equally hard to succeed in.
78
79 You can check out *The Monster Within* on the [Ludum Dare entry page][entry], [on itch.io][itch.io] or [view the source code on github][repo].
80
81 [entry]: http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=28620
82 [itch.io]: http://s0lll0s.itch.io/the-monster-within
83 [repo]: https://github.com/s-ol/ld33
84 [curcur]: http://s0lll0s.itch.io/curved-curse
85
86 [smert_src]: http://ludumdare.com/compo/2015/08/22/what-i-imagine-most-people-are-doing-with-the-theme/
87
88 [love]: https://love2d.org
89 [moonscript]: https://moonscript.org
90 [steering]: http://gamedevelopment.tutsplus.com/series/understanding-steering-behaviors--gamedev-12732
91 [autonom]: http://natureofcode.com/book/chapter-6-autonomous-agents/
92 [rgamedev]: https://reddit.com/r/gamedev
+0
-1
root/blog/ludum_dare_33_postmortem/title: text$plain less more
0 Ludum Dare 33: "The Monster Within" post-mortem
+0
-1
root/blog/self-hosted_virtual_home/categories: text$plain less more
0 update programming devops linux docker
+0
-1
root/blog/self-hosted_virtual_home/date: time$iso8601-date less more
0 2019-03-27
+0
-224
root/blog/self-hosted_virtual_home/text$markdown.md less more
0 In this post I'll break down the setup of my self-hosted virtual home: https://s-ol.nu.
1
2 First a quick overview of what this guide will cover:
3
4 - HTTPS server with multiple subdomains and varying backends
5 - [traefik][traefik] reverse-proxy maintains SSL certificates and serves all requests
6 - [docker-compose][docker-compose] manages running sites/microservices
7 - a private-public git server
8 - access control, management with [gitolite][gitolite]
9 - [klaus][klaus] web frontend for browsing and cloning public repos
10 - fine-grained permissions and SSH public-key access
11 - micro 'CI' setup rebuilds & redeploys docker images when updates are pushed
12
13 **UPDATE (2019-10-03)**: I updated the git hook below to one that supports pushing and building
14 multiple branches based on the `docker-compose.yml`.
15
16 Most of these projects are very well documented so I won't go into a lot of detail on setting them up.
17
18 # HTTPS Server
19 To run multiple subdomains from a single machine, the HTTP requests need to be matched according to the 'Host' header.
20 Most HTTP servers have good facilities for doing this, e.g. apache has vhosts, nginx has server directives etc.
21
22 In the past I had used apache as my main server, which worked well for static content and PHP apps,
23 but not all applications fit into this scheme too well and configuration is a tad tedious.
24
25 With this latest iteration I am using [traefik][traefik] as a "reverse proxy".
26 This means traefik doesn't serve anything (not even static content) by itself,
27 it just delegates requests to one of multiple configured services based on a system of rules.
28
29 It also handles letsencrypt certificate generation and updates out-of-the-box and can be tightly integrated with docker,
30 so that it automatically reacts to new services being added.
31
32 Traefik can be run at system level, but currently I prefer installing the least system-level applications to have my setup
33 as self-contained as possible. Therefore I went with this [traefik in docker-compose][traefik-in-docker] setup from kilian.io:
34
35 version: '3.4'
36
37 services:
38 traefik:
39 image: traefik:1.5-alpine
40 restart: always
41 ports:
42 - 80:80
43 - 443:443
44 networks:
45 - web
46 volumes:
47 - /var/run/docker.sock:/var/run/docker.sock:ro
48 - ./traefik.toml:/traefik.toml
49 - ./acme.json:/acme.json
50 container_name: traefik
51
52 networks:
53 web:
54 external: true
55
56 with the small addition of the `:ro` at the end of the docker socket volume,
57 to prevent attacks on traefik from being able to take over the host docker system (too easily).
58 In the guide you can find more details, including the `traefik.toml` that I am using almost verbatim.
59
60 # Hosting Sites
61 With traefik set up, dockerized services can be added and exposed trivially.
62 For example to start `redirectly`, a tiny link redirect service, this addition suffices:
63
64 redirectly:
65 image: local/redirectly:master
66 restart: always
67 networks:
68 - web
69 labels:
70 - "traefik.frontend.rule=Host:s-ol.nu"
71 - "traefik.enable=true"
72
73 By setting different subdomains in the frontend-rule section, many different services can be provided.
74
75 The image `local/redirectly:git` in this case is built automatically when a repo is pushed (see below).
76
77 note: if a container doesn't have an EXPOSE directive, or EXPOSEs multiple ports,
78 you will have to add a `traefik.port` label specifying which port to use.
79
80 # private/public Git Server
81 While I still have a lot of code on Github, where collaboration is easy,
82 I prefer to own the infrastructure that I store my private projects on.
83 I also wanted to have a public web index of some of the projects.
84
85 The git infrastructure itself is mananged by [gitolite][gitolite], which I really enjoy using.
86
87 repo gitolite-admin @all
88 RW+ = s-ol
89 C = s-ol
90
91 repo public/.*
92 R = @all daemon
93 option writer-is-owner = 1
94
95 repo ... ...
96 RW+ = ludopium
97
98 In the first block I grant myself full access to all repos, as well as the right to automatically create repos by
99 attempting to push/pull from them.
100
101 The second block makes all repos prefixed with `public/` readable by anyone in the gitolite system,
102 as well as the `git-daemon`, which allows cloning via `git://....` access (port 9418).
103 The `write-is-owner` option lets me set the git `description` field using `ssh git@git.s-ol.nu desc`.
104
105 I chose the `public/` prefix because it results in all public repos being stored in one directory together
106 (`/var/lib/gitolite/repositories/public`), where klaus can easily pick them up.
107
108 The klaus web frontend is set up using traefik above like so:
109
110 klaus:
111 image: hiciu/klaus-dockerfile
112 restart: always
113 networks:
114 - web
115 volumes:
116 - /var/lib/gitolite/repositories/public:/srv/git:ro
117 command: /opt/klaus/venv/bin/uwsgi --wsgi-file /opt/klaus/venv/local/lib/python2.7/site-packages/klaus/contrib/wsgi_autoreload.py --http 0.0.0.0:8080 --processes 1 --threads 2
118 environment:
119 KLAUS_REPOS_ROOT: /srv/git
120 KLAUS_SITE_NAME: git.s-ol.nu
121 KAUS_CTAGS_POLICY: tags-and-branches
122 KLAUS_USE_SMARTHTTP: y
123 labels:
124 - "traefik.frontend.rule=Host:git.s-ol.nu"
125 - "traefik.enable=true"
126
127 I am using the ['autoreload' feature][klaus-autoreload] and the `hiciu/klaus-dockerfile` docker image.
128 Setting `KLAUS_USE_SMARTHTTP` allows cloning repos via HTTP.
129
130 In the future I would like to modify klaus a bit, for example by showing the README in the root of a project per default
131 and applying a custom theme.
132
133 # Micro-CI
134 The last piece of the puzzle is automatically deploying projects whenever they are pushed.
135 This can be realized using git's `post-receive` hooks and is generally pretty well known.
136
137 I followed this gitolite guide for [storing repo-specific hooks in the gitolite-admin repo][gitolite-hooks].
138 It requires a change in the gitolite rc file (on the server), but after that you can configure deployment processes in the conf like this:
139
140 @dockerize = public/redirectly ...
141 @jekyllify = blog
142
143 repo @dockerize
144 option hook.post-receive = docker-deploy
145
146 # i actually dont have a jekyll blog anymore but its an easy one as well
147 repo @jekyllify
148 option hook.post-receive = jekyll-deploy
149
150 The hooks are stored in the same repo under `local/hooks/repo-specific`.
151 Here is the `docker-deploy` hook I am using:
152
153 #!/bin/bash
154 set -e
155
156 while read oldrev newrev refname
157 do
158 BRANCH="$(git rev-parse --symbolic --abbrev-ref $refname)"
159
160 # Get project name
161 PROJECT="$PWD"
162 PROJECT="${PROJECT#*/repositories/public/}"
163 PROJECT="${PROJECT#*/repositories/}"
164 PROJECT="${PROJECT%.git}"
165 PROJECT="$(echo "$PROJECT" | tr "/ " "-_")"
166
167 # Paths
168 CHECKOUT_DIR=/tmp/git/$PROJECT
169 TARGET_DIR=/home/s-ol/aerol
170 IMAGE_NAME=local/$PROJECT:$BRANCH
171
172 # this one doesn't require python & yq, but it means the container has to run already...
173 # SERVICES=$(docker ps --filter "ancestor=${IMAGE_NAME}" --format '{{.Label "com.docker.compose.service"}}' \
174 # | sort | uniq)
175
176 SERVICES=$(yq -r <"$TARGET_DIR/docker-compose.yml" \
177 ".services | to_entries | map(select(.value.image == \"${IMAGE_NAME}\").key) \
178 | join(\" \")")
179
180 if [ -z "$SERVICES" ]; then
181 continue
182 fi
183
184 mkdir -p "$CHECKOUT_DIR"
185 GIT_WORK_TREE="$CHECKOUT_DIR" git checkout -q -f $newrev
186 echo -e "\e[1;32mChecked out '$PROJECT'.\e[00m"
187
188 cd "$CHECKOUT_DIR"
189 docker build -t "$IMAGE_NAME" .
190 echo -e "\e[1;32mImage '$IMAGE_NAME' built.\e[00m"
191
192 cd "$TARGET_DIR"
193 docker-compose up -d $SERVICES
194 echo -e "\e[1;32mService(s) '$SERVICES' restarted.\e[00m"
195 done
196
197 It will build a `local/$REPO:$BRANCH` image whenever you push, then run `docker-compose up -d $SERVICES` in `$TARGET_DIR`,
198 where `$SERVICES` are all the docker-compose services that use the image. If there are none, no image will be built.
199 For this to work it has to parse the `docker-compose.yaml` file, which means you have to install [`yq`][yq] and `jq`, e.g. on Ubuntu:
200
201 sudo apt-get install jq python3-pip
202 sudo pip install yq
203
204 If you would like to avoid that, you can use the commented command for `SERVICES=` above, which only relies on docker itself,
205 the only problem is that you will have to do the first build manually (or re-tag a dummy image) before the first build,
206 since it can only detect containers that are already running.
207
208 ---
209
210 That's basically it!
211 If you have questions or comments i'll be happy to hear from you on twitter, github or [mastodon][merveilles].
212
213 [traefik]: https://traefik.io/
214 [docker-compose]: https://docs.docker.com/compose/
215 [gitolite]: http://gitolite.com/gitolite/index.html
216 [klaus]: https://github.com/jonashaag/klaus
217
218 [traefik-in-docker]: https://blog.kilian.io/server-setup/
219 [klaus-autoreload]: https://github.com/jonashaag/klaus/wiki/Autoreloader
220 [gitolite-hooks]: http://gitolite.com/gitolite/cookbook#v36-variation-repo-specific-hooks
221 [yq]: https://github.com/kislyuk/yq
222
223 [merveilles]: https://merveilles.town/@s_ol
+0
-1
root/blog/self-hosted_virtual_home/title: text$plain less more
0 my self-hosted Virtual Home
+0
-7
root/blog/stencils_101/$order less more
0 suits_final
1 killbill_progress
2 killbill_final
3 killbillstencil
4 balistencil_final
5 poster
6 technofist_final
root/blog/stencils_101/balistencil_final/image$jpeg.jpg less more
Binary diff not shown
+0
-1
root/blog/stencils_101/categories: text$plain less more
0 update stencils art
+0
-1
root/blog/stencils_101/date: time$iso8601-date less more
0 2015-06-29
root/blog/stencils_101/killbill_final/image$jpeg.jpg less more
Binary diff not shown
root/blog/stencils_101/killbill_progress/image$jpeg.jpg less more
Binary diff not shown
root/blog/stencils_101/killbillstencil/image$png.png less more
Binary diff not shown
root/blog/stencils_101/poster/image$jpeg.jpg less more
Binary diff not shown
root/blog/stencils_101/suits_final/image$jpeg.jpg less more
Binary diff not shown
root/blog/stencils_101/technofist_final/image$jpeg.jpg less more
Binary diff not shown
+0
-87
root/blog/stencils_101/text$markdown.md less more
0 So, for the first non-meta post (don't mind this line), I'm gonna walk you through my stencil creation process.
1
2 # Design
3 Right now, I design all my stencils in photoshop (CS6, running in wine).
4 If I get better at hand drawing maybe one day I will sketch them up freehand right on the material but as of now I only do that for text.
5
6 As an example, let's take the Kill Bill Stencil I did a few days ago:
7
8 <mmm-embed path="killbill_final"></mmm-embed>
9
10 The first thing I did was create a new file in Photoshop, with the dimensions set to what I can print with my inkjet printer (International - A4).
11 For the design process the canvas size doesn't really matter anyway and I like to work on a familiar size so I can judge how fine the details should be, also I print the images via my phone and so I need to have them in A4 size to get consistent results later anyway.
12
13 The next thing I did was look for a picture to use, after a (very) quick google images tour I settled on this image:
14
15 <mmm-embed path="poster"></mmm-embed>
16
17 In order to turn this into a stencil, I used the *Threshold* Image Adjustment, but before that I cut away all the background.
18 With the Magic Wand and Quick Selection, I removed the black bar and yellow background.
19 Because I am living in constant fear of losing work, I duplicated the layer and then popped open the *Threshold* Dialog (`Image > Adjustements > Threshold`).
20 I played around with the slider until I got a good ratio of light and shadow and then applied the changes.
21
22 Often some parts of the image require a different threshold setting than other parts (for example the faces often have lower contrast and require a different threshold value).
23 In those cases, I just duplicate the original resource layer, find the other value and then cut away the pieces that I don't need
24 (because I am afraid of commitment I sometimes end up masking the layers with vector masks instead).
25
26 After this step, there will be a lot of tiny artifacts and bubbles that are way too tiny to be cut out (or impossible because they would form tiny islands).
27 For the Kill Bill Stencil, I went over __all__ of those by hand on another layer with the pencil tool (the brush is terrible for stencilwork because you want a hard edge to cut later, not a gradient around what you drew).
28 However later on I realized that there is a good way to reliably eliminate this noise; all you need to do is apply a blur (*Gaussian Blur* is very effective and configurable, `Filter > Blur > Gaussian Blur`) and then re-apply *Threshold*.
29 You can play around with this process a bit to get a feeling for how hard you need to blur to eliminate the noise.
30
31 You will still need to manually touch up the stencil though, at least to look for and fix *Islands*;
32 In my case, I spraypaint black color onto a white background, so everything that is black gets cut out.
33 This means that every white part that is surrounded completely by black is going to fall out of the stencil together with the black aswell.
34 Larger islands may be taped to the surface you are spraying on for art, but it's best to avoid Islands wherever possible (and especially small ones).
35
36 To visualize the final stencil, I have looked up the exact colors of the spraypaint I own at the manufacturers website and saved them as swatches in my photoshop.
37 I use those to put a background behind the whole image and to color layers that I want to spraypaint in color later.
38
39 <mmm-embed path="killbillstencil"></mmm-embed>
40
41 One problem when you like working non-destructively on many layers, as I do, is that you normally cannot remove something on a layer below by painting over it.
42 There are two options, either you can just draw the background color, which means you will have to flatten the image later to get the outline, or you can use the *Layer Blend Modes* to your advantage:
43 What I did was put all the layers in a Layer Group, and set that groups *Blend Mode* to *Screen*.
44 This will result in everything Black staying Black and everything White turning into transparent, so now you can just paint white on a layer above one that is black to erase the one below.
45
46 # Printing
47 To print the stencil out, I apply a *Stroke* to all the seperate color's layers (`Layer Styles > Stroke`) and turn down the *Fill* value to 0%.
48 That way I save toner when printing and have a clear outline guide to cut at.
49 If one color is scattered across layers I either merge them or put them in a group, then apply the styles to that group instead.
50
51 Because my printer's drivers are weird I print with the phone app. I save every color's outline as a seperate JPG, push them to my phone and print them.
52 I used to print on standard A4 paper, tape that to thicker cardboard and cut, but I realized that my printer can actually handle the thicker cardbord paper I have.
53 That makes cutting a lot easier.
54
55 # Cutting
56 Cutting is very straightforward, I just try to get as many details as possible.
57 I use a standard box cutter but a scalpel / x-acto knife would probably work even better.
58 I have a rubber cutting mat that is specifically made for this and works very well.
59
60 <mmm-embed path="killbill_progress"></mmm-embed>
61
62 For small round holes I use an old screwdriver part that turned out to be a perfect hole-punching tool and hit it into the paper with a hammer (on a piece of wood).
63
64 # Painting
65 To hold down the stencils I usually tape them to the surface with painters tape.
66 Sometimes I just hold them or press parts flat.
67 If there are small, flimsy pieces inside that won't stay on the surface or that would get blown to the side or up by the aerosol, I make a small loop out of the tape and stick it to the surface with that makeshift double-sided tape.
68
69 Then I just grab the spraycans and paint the stencil with small, short strokes.
70 I try not to hit the same spot too often or too long so the paint doesn't flow beneath the stencil or take too long to dry.
71
72 # Results
73 Here are some of my stencils:
74
75 <mmm-embed path="killbill_final"></mmm-embed>
76 <mmm-embed path="technofist_final"></mmm-embed>
77 <mmm-embed path="suits_final"></mmm-embed>
78 <mmm-embed path="balistencil_final"></mmm-embed>
79
80 I tweet all my daily stencils with the [hashtag #astenciladay on twitter][#astenciladay] and post them in the [*Daily Stencil Art* streak on *streak.club*][dailystencil].
81
82 If you want any of the `psd`s or the printable outline `jpg`s, [shoot me a tweet][twitter]!
83
84 [#astenciladay]: https://twitter.com/hashtag/astenciladay
85 [dailystencil]: https://streak.club/s/614/daily-stencil-art
86 [twitter]: https://twitter.com/S0lll0s
+0
-1
root/blog/stencils_101/title: text$plain less more
0 Stencils 101
+0
-5
root/blog/stretching_gates/$order less more
0 case
1 stripboard
2 schematic
3 finished_case
4 crude
root/blog/stretching_gates/case/image$jpeg.jpg less more
Binary diff not shown
+0
-1
root/blog/stretching_gates/categories: text$plain less more
0 update gamedev electronics
root/blog/stretching_gates/crude/image$png.png less more
Binary diff not shown
+0
-1
root/blog/stretching_gates/date: time$iso8601-date less more
0 2017-07-04
+0
-1
root/blog/stretching_gates/finished_case/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/881940776749543427
root/blog/stretching_gates/schematic/image$jpeg.jpg less more
Binary diff not shown
root/blog/stretching_gates/stripboard/image$jpeg.jpg less more
Binary diff not shown
+0
-85
root/blog/stretching_gates/text$markdown.md less more
0 As mentioned in the last post, there was some room for improvement in the last iteration of the
1 trigger/divider circuit. While the divider was working great as a clock source for the drum
2 machine, the signal output by the comparator was very short and sometimes got triggered twice
3 with one acoustic hit on the piezo when the reference voltage was not perfectly tuned.
4 On the other hand the outputs of the 4017 always stay on or off for a whole step.
5
6 While the long steps can be useful to make an oscillator play long notes, shorter pulses each step
7 are much more interesting to create drum-ish sounds.
8 Here's a crude drawing of how what the circuit was doing vs how it's supposed to work:
9
10 <mmm-embed path="crude"></mmm-embed>
11
12 The black lines are the signal levels 'as they used to be': you can see that the comparator output
13 has very short pulses of more or less random duration, the /2 output is on half of the time, the /3
14 one third of the steps and so on.
15 Given some way of creating the blue signal in the top diagram, that stays on a specified amount of
16 time after each trigger and only then drops back down, the other two blue graphs are very easy to
17 obtain by simply logical-AND-ing the shaped pulse signal and the divided signal for each divison.
18 Conveniently, there is a CMOS chip with four AND gates that are more than fast enough: the 4081.
19
20 ## hold on
21 The only missing part is the one that needs to hold the signal for a specified time on each trigger.
22 Well, the famous 555 Timer IC has our back: in the *monostable Configuration* it will do just that.
23 Here's a schematic of the basic circuit (pic taken from [here][555-src]):
24
25 ![monostable][monostable.jpg]
26
27 This is really half as bad as it looks, but the details are better read up on elsewhere.
28 Basically, whenever the signal on the trigger (pin 2) goes __low__, the output (pin 3) goes high
29 and the capacitor between threshold (pin 6) and ground is charged.
30 When it reaches a certain charge, the output drops back to low and the capacitor is drained.
31
32 This is exactly what is needed, except that the triggers has to go low here to start the timer.
33 To fix this, I simply swapped the inverting and non-inverting inputs of the comparator, so that it
34 always outputs the opposite result.
35
36 ## putting it together
37 Here's the final schematic as I built it:
38
39 <mmm-embed path="schematic"></mmm-embed>
40
41 Some things you may notice:
42
43 1. I put a potentiometer into the 555s charging path so that the pulse length can be adjusted.
44 2. I added a switch that allows me to decide whether I want to AND the divided outputs with the
45 generated pulse, or basically disable that part by putting 9V there, which basically turns the
46 AND gate into a normal wire.
47 3. There are diodes on the reset pins so that I can later expand this with a reset input jack.
48 4. I am using both halves of the LM393 with the inputs swapped: the positive-going pulse goes to an
49 LED that shows me the 'raw' incoming signal, and the negative goes to the 555, with another LED
50 showing the 'shaped' one. With piezo triggers it is impossible to even see the raw LED turn on,
51 but if I use this with another clock source (LFO for example) it might be useful.
52 5. We're kind of low on switches so I scrapped that part, but I wanted another switch to choose
53 between the 555 output or the other LM393 output to be fed into the 4017 dividers. That way I
54 would've gotten to keep the beat-skipping properties independently of the gate length control,
55 but now I have to lower the gate length to minimum to disable the 555s debouncing effects.
56
57 ## wrapping it up
58 After this was all working on the breadboard, I started to solder a second version on stripboard.
59 Before finishing the design I sketched some stripboard layouts on a piece of paper, but in the end I
60 threw all care overboard and just placed components on the stripboard.
61
62 <mmm-embed path="stripboard"></mmm-embed>
63
64 I cut apart a large IC socket and soldered the two halfes to the sides of the board to simplify
65 testing and panel wiring, but I'm not sure if it was the best idea bceause the sockets aren't really
66 made to be used over and over and the wires dont hold too well. I'm thinking I might tape over the
67 sockets once all the cables are in place and tested.
68
69 [Juan][juan] found this great old Video casette case as a chassis and I really liked it.
70 We used the Dremel that we had luckily gotten our hands on and started drilling holes for the audio
71 jacks with a template made from paper.
72
73 <mmm-embed path="case"></mmm-embed>
74
75 The inside also needed some plastic spines ground away to make room for the jacks, but the Dremel
76 made quick work of all that. Juan finished the top of the case with mounting holes for the two
77 potentiometers, the input jack, the switch, and two hot-glue-covered holes for the LEDs to shine
78 through.
79
80 <mmm-embed path="finished_case" nolink></mmm-embed>
81
82 [juan]: https://twitter.com/juanorloz
83 [555-src]: https://electrosome.com/monostable-multivibrator-555-timer/
84 [monostable.jpg]: https://electrosome.com/wp-content/uploads/2013/05/Monostable-Multivibrator-using-555-Timer-Circuit-Diagram.jpg
+0
-1
root/blog/stretching_gates/title: text$plain less more
0 Stretching Gates
+0
-21
root/blog/text$moonscript -> fn -> mmm$dom.moon less more
0 import div, h3, a, p, ul, li from require 'mmm.dom'
1 import link_to from (require 'mmm.mmmfs.util') require 'mmm.dom'
2 import ropairs from require 'mmm.ordered'
3
4 =>
5 div {
6 h3 link_to @
7 ul do
8 posts = { (p\gett 'date: time/unix'), p for p in *@children }
9
10 posts = for date, post in ropairs posts
11 continue if post\get 'hidden: bool'
12 li (link_to post, os.date '%F', date), ' - ', post\gett 'title: mmm/dom'
13
14 posts
15 p {
16 "also check out my weekly posts for the 2020 FabAcademy on my "
17 a "fabcloud page", href: 'https://fabacademy.org/2020/labs/opendot/students/sol-bekic/'
18 "."
19 }
20 }
+0
-8
root/blog/video_synth_research/$order less more
0 visualist
1 bent1
2 LZX_reel
3 sketch_titler
4 bent2
5 schematic
6 ich_bin_holz
7 mine
+0
-1
root/blog/video_synth_research/LZX_reel/URL -> youtube$video less more
0 https://www.youtube.com/watch?v=Aba7VD4h6G4
+0
-1
root/blog/video_synth_research/bent1/URL -> youtube$video less more
0 https://www.youtube.com/watch?v=CwVHHz3ph_c
+0
-1
root/blog/video_synth_research/bent2/URL -> youtube$video less more
0 https://www.youtube.com/watch?v=odBDytzF7ko
+0
-1
root/blog/video_synth_research/categories: text$plain less more
0 update gamedev electronics
+0
-1
root/blog/video_synth_research/date: time$iso8601-date less more
0 2017-06-26
root/blog/video_synth_research/ich_bin_holz/image$jpeg.jpg less more
Binary diff not shown
root/blog/video_synth_research/mine/image$jpeg.jpg less more
Binary diff not shown
root/blog/video_synth_research/schematic/image$png.png less more
Binary diff not shown
+0
-1
root/blog/video_synth_research/sketch_titler/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/876077574639767552
+0
-93
root/blog/video_synth_research/text$markdown.md less more
0 When we started the *Synths and Stuff* project, we discussed how it would be cool to have video
1 synthesis to accompany the audio performance. In a modular synth setup, we would be able to patch
2 audio and control signals into the video phase to make music reactive visuals.
3
4 ## expensive toys
5 While I love video synthesis, and my last project before this [was sort of that][plonat-atek], I was
6 initially a bit worried about the complexity of it.
7 I had found out about [LZX Industries][lzx] a few months ago, who make amazing modular video synth
8 gear... that is very expensive. It looks amazing though:
9
10 <mmm-embed path="LZX_reel" nolink></mmm-embed>
11
12 I think the price is probably justified, as a lot of engineering goes into making this work in the
13 first place, and it looks very clean, which I would attribute to high quality components.
14
15 However, for our project we don't strive to create perfect and clean audio or video.
16 Glitches and noise are an important part of the analog aesthetic and make the results feel as
17 organic as they do.
18
19 I also found the *3trins-RGB+1c*, which looks like a nifty little thing but is also way out of my
20 budget.
21
22 ## visualising another way
23 Reinforced with these thoughts I started looking for DIY video modules. At least in relation to
24 audio schematics, there is *very little* content on the internet.
25 However I did find a few good resources.
26
27 There is a 2013 blog with only a few posts, but it does a great job of filtering out relevant
28 information in [this post summarizing forum gold][vfold].
29
30 This led me onto the track of the MC1377 chip, an RGB-to-PAL/NTSC video encoder IC.
31 At some point I found this video showcasing a video effect device based on it, the *Visualist*:
32
33 <mmm-embed path="visualist" nolink></mmm-embed>
34
35 The best thing is that there is [awesome documentation][visualist] (hotlinked from original dropbox,
36 I have a copy if it goes down) for this thing.
37 It's not very detailed but the circuit is rather minimal and the color logic easily removed.
38 The stripped down version that parses an incoming video signal brightness and encodes RGB values you
39 make from that as the signals scans over the screen is handleable even if not completely understood.
40
41 To practice my understanding and work it into my brain, I broke the original schematic out into
42 logical units with single connections.
43
44 My plan is to make the *Visualist* color logic, that pseudo-randomly assigns a color to each
45 brightness level in a 7-step scale that you can adjust into a small module that can be used,
46 but also mixed with other effects (perhaps an oscillator and external color input) so that we can
47 sync to our music control signals.
48
49 <mmmdom path="schematic"></mmmdom>
50 <mmmdom path="mine"></mmmdom>
51
52 If you look closely you can see that I left some parts out and added a bit of logic to allow setting
53 the amount of steps the brightness scale is sliced into.
54
55 ## OBS -2.3
56 The other videoish thing we have going on sofar is this:
57
58 <mmm-embed path="sketch_titler" nolink></mmm-embed>
59
60 this *SONY Family Studio* 'Video Sketch Titler' is basically a little box that you connect inbetween
61 your video camera with some awesome family vacation clips on tape and your VHS recorder, which is
62 hooked up to your PC.
63
64 You can then draw an amazing title screen over your video with this touchpad-ish device.
65 When you are ready, you rewind the camera to the beginning, press play and start recording on VHS at
66 the same time. Then you hit the 'fade in' and 'fade out' buttons on the Sketch Titler and go win
67 that home video contest.
68
69 Well, this is how it's intended to be used anyway. If your camera doesn't work (like mine), you can
70 instead practice on a gray background:
71
72 <mmmdom path="ich_bin_holz"></mmmdom>
73
74 So ideally, we can get a live camera as an input to this, and draw on it in *real time*!
75 Like an ancient livestreaming tool like OBS.
76 (and then hopefully we will also have a Visualist to either hook up before or after this device)
77
78 Also some other people else has already circuit-bent this.
79 It looks amazing, so we might just give that a go aswell:
80
81 <mmm-embed path="bent1" nolink></mmm-embed>
82 <mmm-embed path="bent2" nolink></mmm-embed>
83
84 Since taking this for a spin and making a BOM for the *Visualist*,
85 I haven't spent any more time on the *Visualist* and turned to the audio side of things for now.
86 The next step on this front will probably be to lay out (parts of) the circuitry on stripboard and'
87 get ready to solder :)
88
89 [plonat-atek]: https://s-ol.itch.io/plonat-atek
90 [lzx]: https://www.lzxindustries.net/
91 [vfold]: https://vfoldsynth.wordpress.com/2013/01/23/hidden-stores-of-forum-gold/
92 [visualist]: https://www.dropbox.com/s/uhjd2e6gur972yo/VisualistKl.pdf?dl=0
+0
-1
root/blog/video_synth_research/title: text$plain less more
0 Video Synth Research
+0
-1
root/blog/video_synth_research/visualist/URL -> youtube$video less more
0 https://www.youtube.com/watch?v=99j3V9t26pY
+0
-1
root/blog/why_redirectly/categories: text$plain less more
0 update programming devops
+0
-1
root/blog/why_redirectly/date: time$iso8601-date less more
0 2019-10-26
+0
-56
root/blog/why_redirectly/text$markdown.md less more
0 # Why I'm running a personal URL shortening service
1 This blog you are reading at the moment is currently running it's third reincarnation.
2 Originally, it was a Jekyll blog hosted on github pages,
3 before I moved to my own web platform.
4 That platform is currently undergoing its second big rewrite.
5
6 Even while still running on github pages,
7 the blog moved from https://s0lll0s.github.io, to https://s0lll0s.me and finally to https://s-ol.nu.
8 Once arrived at https://s-ol.nu, I had to move the blog posts to a different route (`/blog` vs just living in the root)
9 at some point, to make space for other routes.
10 Then with my custom platform everything changed again,
11 since now my main system is actually located at https://mmm.s-ol.nu.
12
13 As you might imagine, at every change all the old URLs stop working.
14 Noone likes finding dead links, but if you post links on social media
15 it is often extremely unwieldy or impossible to update all the posts.
16
17 If you run your own website or blog ([maybe][sir] you [should][indie]?),
18 your site may have gone throug similar transitions,
19 or maybe it is about to go through one -
20 or perhaps you are deferring making a change because you worry about this?
21
22 To partially solve this issue, I built a tiny URL redirection service, [redirectly][redirectly].
23 It works much the same as those URL shortening services you may have used before (bit.ly, tinyurl etc.),
24 except that I manually specify the shortened URLs in a file (in the git repo).
25
26 Nowadays, when I post a link to some content on my website (such as this blog post),
27 I first allocate a new `slug` (shortlink) and set it up in `redirectly`.
28 Then I use the shortened link, such as https://s-ol.nu/why-redirectly in this case,
29 instead of directly linking to the content.
30
31 This way, whenever I make changes to my content's adressing scheme,
32 I simply change the URL location, and any old links that are floating around remain functional.
33 It's also helpful to direct people to the best documentation for a particular project:
34 when I start working on something, it might exist only as a git repo,
35 but later in the project's lifecycle I may add a descriptive article on my website or as part of the blog.
36 Perhaps one of my projects will outgrow this website and need its own domain some time.
37 By always linking using a canonical project-URL, I can make sure that old links always point to the best place.
38 Also if I ever decide to move to a different domain again,
39 I can simply leave the redirection service running on here, at least for a few years :)
40
41 Of course all of this doesn't work when visitors of my page navigate around by themselves,
42 and then share the URL from their address bar.
43 This could be solved by using the JS [history API][history] by overriding the displayed URL with the permalink whenever one exists,
44 but I haven't tried implementing this type of bi-directional querying yet.
45
46 I am also aware that running Clojure (and therefore a JVM) is not necessarily
47 the best choice for a service that is so light,
48 but I wrote `redirectly` as a little experiment while learning Clojure,
49 and it is such a simple project that if it ever bugs me I can just throw it out and implement it in something else.
50
51 [sir]: https://drewdevault.com/make-a-blog
52 [indie]: https://indieweb.org/why
53
54 [redirectly]: https://s-ol.nu/redirectly/src
55 [history]: https://developer.mozilla.org/en-US/docs/Web/API/History_API
+0
-1
root/blog/why_redirectly/title: text$plain less more
0 Why I'm running a personal URL shortening service
+0
-1
root/description: text$plain less more
0 mmm is not the www, because it runs on MoonScript.
+0
-5
root/experiments/$order less more
0 tags
1 glitch_cube
2 torus4d
3 center_of_mass
4 parallax_panels
+0
-1
root/experiments/center_of_mass/_web_view: type less more
0 text/html+interactive
+0
-1
root/experiments/center_of_mass/description: text$plain less more
0 Fonts aligned by Center-of-Mass
+0
-171
root/experiments/center_of_mass/text$moonscript -> mmm$dom.moon less more
0 assert MODE == 'CLIENT', '[nossr]'
1
2 import CanvasApp from require 'mmm.canvasapp'
3 import rgb from require 'mmm.color'
4 import article, h1, p, div, span, input, button from require 'mmm.dom'
5
6 fast = true
7 center = true
8 center_char = do
9 canvas = document\createElement 'canvas'
10 ctx = canvas\getContext '2d'
11
12 cache = {}
13
14 (char, font, height) ->
15 name = "#{char} #{height} #{font}"
16 return table.unpack cache[name] if cache[name]
17
18 ctx\resetTransform!
19 ctx.font = "#{height}px #{font}"
20 width = (ctx\measureText char).width
21 canvas.width, canvas.height = width, height * 1.2
22
23 ctx.font = "#{height}px #{font}"
24 ctx.textBaseline = 'top'
25 ctx.fillStyle = rgb 0, 0, 0
26 ctx\fillText char, 0, 0
27
28 data = ctx\getImageData 0, 0, width, height * 1.2
29
30 local xx, yy
31 if fast
32 loop = window\eval '(function(data) {
33 var xx = 0, yy = 0, n = 0;
34 for (var x = 0; x < data.width - 1; x++) {
35 for (var y = 0; y < data.height - 1; y++) {
36 var i = y * (data.width * 4) + x * 4;
37 var alpha = data.data[i + 3] / 255;
38 xx += x * alpha;
39 yy += y * alpha;
40 n += alpha;
41 }
42 }
43
44 xx /= n;
45 yy /= n;
46 return [xx, yy];
47 })'
48 res = loop nil, data
49 xx, yy = res[0], res[1]
50 else
51 xx, yy, n = 0, 0, 0
52 for x = 0, data.width - 1
53 for y = 0, data.height - 1
54 i = y * (data.width * 4) + x * 4
55 alpha = data.data[i + 3] / 255
56 xx += x * alpha
57 yy += y * alpha
58 n += alpha
59
60 xx /= n
61 yy /= n
62 cache[name] = { xx, yy, width }
63 xx, yy, width
64
65 class CenterOfMass extends CanvasApp
66 width: window.innerWidth - 20
67 height: 300
68 new: (text, @font, @size) =>
69 super!
70 @text = {}
71 for i = 1,#text
72 @add text\sub i, i
73
74 add: (char) =>
75 rcx, rcy, w = center_char char, @font, @size
76 cx, cy = w/2, @size/2
77 vx, vy = 0, 0
78 table.insert @text, {
79 :char, :rcx, :rcy, :w
80 :cx, :cy, :vx, :vy
81 }
82
83 refresh: =>
84 for char in *@text
85 char.rcx, char.rcy, char.w = center_char char.char, @font, @size
86
87 keydown: (key) =>
88 if key == "Backspace" or key == "Delete"
89 table.remove @text
90 elseif string.len(key) == 1
91 @add key
92
93 update: (dt) =>
94 super dt
95
96 ACCEL = 4 * dt
97 DAMPING = 8 * dt
98
99 for char in *@text
100 { :rcx, :rcy, :cx, :cy, :w } = char
101 if not center
102 rcx, rcy = w/2, @size/2
103 dx, dy = rcx - cx, rcy - cy
104 char.vx += dx * ACCEL
105 char.vy += dy * ACCEL
106 char.cx += char.vx
107 char.cy += char.vy
108 char.vx *= DAMPING
109 char.vy *= DAMPING
110
111 draw: =>
112 @ctx\clearRect 0, 0, @width, @height
113
114 @ctx.font = "#{@size}px #{@font}"
115 @ctx.textBaseline = 'top'
116
117 x, y = @size * .1, @size
118 for { :char, :cx, :cy, :w } in *@text
119 if x + w > @width
120 x = 0
121 y += @size * 1.2
122
123 @ctx\fillText char, x + w/2 - cx, y - cy
124 x += w
125
126 _content = {}
127 append = (x) -> table.insert _content, x
128
129 append h1 'Fonts aligned by Center-of-Mass'
130 app = CenterOfMass "Click here and type Away!", "Times New Roman", 40
131 append app.canvas
132 app.canvas.style.backgroundColor = '#eee'
133
134 add = =>
135 append div {
136 span 'font: ',
137 with @font_input = input!
138 .type = 'text'
139 .value = 'Times New Roman'
140 with button 'set'
141 .onclick = (_, e) ->
142 app.font = @font_input.value
143 app\refresh!
144 }
145
146 append div {
147 span 'size: ',
148 input type: 'range', min: 2, max: 120, value: 40, onchange: (_, e) ->
149 size = e.target.value
150 @size_label.innerText = size
151 app.size = size
152 app\refresh!
153 with @size_label = span '40'
154 ''
155 }
156
157 append div {
158 span 'center characters by weight: ',
159 input type: 'checkbox', checked: center, onchange: (_, e) ->
160 center = e.target.checked
161 }
162
163 append div {
164 span 'optimize inner loop: ',
165 input type: 'checkbox', checked: fast, onchange: (_, e) ->
166 fast = e.target.checked
167 }
168 add {}
169
170 article _content
+0
-1
root/experiments/glitch_cube/description: text$plain less more
0 program interpreting random parts of itself as textures for a cube.
+0
-1
root/experiments/glitch_cube/link: URL -> tweet less more
0 https://twitter.com/S0lll0s/status/984465155445678080
+0
-2
root/experiments/parallax_panels/$order less more
0 picture
1 viewer
+0
-1
root/experiments/parallax_panels/description: text$plain less more
0 A Parallax SVG Viewer, for Prototyping (Eurorack) Panels
+0
-1
root/experiments/parallax_panels/picture/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/1141006444793405440
+0
-16
root/experiments/parallax_panels/text$markdown.md less more
0 parallax-panels
1 ===============
2
3 I'm prototyping an aesthetic for (eurorack) panels that relies on multiple layers of parallax visuals for dramatic effect.
4
5 <mmm-embed path="picture" nolink></mmm-embed>
6
7 parallax-viewer
8 ===============
9
10 I built a little SVG viewer that stacks the layers and lets you view them in parallax.
11 You can find it <mmm-link path="viewer">here</mmm-link>.
12
13 And here's a little demonstration:
14
15 <mmm-embed path="viewer/demo" nolink></mmm-embed>
+0
-1
root/experiments/parallax_panels/viewer/$order less more
0 demo
root/experiments/parallax_panels/viewer/demo/video$mp4.mp4 less more
Binary diff not shown
+0
-1
root/experiments/parallax_panels/viewer/link: URL less more
0 https://codepen.io/s-ol/full/rExrey
+0
-1
root/experiments/tags/_web_view: type less more
0 text/html+interactive
+0
-1
root/experiments/tags/description: text$plain less more
0 defining toggles, categories etc. with tags and functional hooks
+0
-1
root/experiments/tags/hidden: text$lua -> bool less more
0 return true
+0
-146
root/experiments/tags/tags: text$moonscript -> table.moon less more
0 join = (tbl, sep) ->
1 ret = ''
2 for tag in pairs tbl
3 ret ..= (tostring tag) .. sep
4 ret
5
6 handlers = {
7 add: {}
8 rmv: {}
9 }
10
11 class Node
12 new: (@name) =>
13 @tags = {}
14
15 inspect: =>
16 "#{@name}: [#{join @tags, ' '}]"
17
18 has: (tag) => @tags[tag]
19 add: (tag) => @tags[tag] = tag
20 rmv: (tag) => @tags[tag] = nil
21
22 any = -> true
23 literal = (def) -> (val) -> def == val
24 oneof = (defs) -> (val) ->
25 for def in *defs
26 return true if def == val
27 false
28 has = (tag) -> (node) -> node\has tag
29
30 add_tag = (node, tag) ->
31 return if node\has tag
32 node\add tag
33 for hand, _ in pairs handlers.add
34 hand\match node, tag
35
36 rmv_tag = (node, tag) ->
37 return if not node\has tag
38 node\rmv tag
39 for hand, _ in pairs handlers.rmv
40 hand\match node, tag
41
42 class Handler
43 new: (@rule, @action, match, @func) =>
44 @args = for arg in *match
45 if 'string' == type arg
46 literal arg
47 elseif 'table' == type arg
48 oneof arg
49 else
50 arg
51
52 match: (...) =>
53 supplied = { ... }
54 assert #supplied == #@args, 'length of arguments doesnt match'
55 for i = 1, #supplied
56 return false if not @args[i] supplied[i]
57
58 @.func @rule, ...
59
60 name: => "#{@rule.name}:#{@action}"
61
62 class Rule
63 new: (@name="#{@@__name}") =>
64 @owned_handlers = {}
65
66 hook: (action, ...) =>
67 handler = Handler @, action, ...
68 table.insert @owned_handlers, handler
69 handlers[action][handler] = handler
70
71 destroy: =>
72 for hand in *@owned_handlers
73 handlers[hand.action][hand] = nil
74
75 class Hierarchy extends Rule
76 new: (@parent, @child) =>
77 super!
78
79 -- when something is tagged with the child-tag, apply the parent tag
80 @hook 'add', { any, @child }, (node) =>
81 add_tag node, @parent
82
83 -- when child tag is removed, remove parent tag
84 @hook 'rmv', { any, @child }, (node) =>
85 rmv_tag node, @parent
86
87 -- when parent tag is removed, remove child tag
88 @hook 'rmv', { any, @parent }, (node) =>
89 rmv_tag node, @child
90
91 class Toggle extends Rule
92 new: (@a, @b) =>
93 super!
94
95 either = { @a, @b }
96 opposite = (tag) -> if tag == @a then @b else @a
97
98 -- when a is added, remove b and vice-versa
99 @hook 'add', { any, either }, (node, tag) =>
100 rmv_tag node, opposite tag
101
102 -- when a is removed, add b and vice-versa
103 @hook 'rmv', { any, either }, (node, tag) =>
104 add_tag node, opposite tag
105
106 class NamespacedToggle extends Rule
107 new: (@ns, @a, @b) =>
108 super!
109
110 namespaced = has @ns
111 either = { @a, @b }
112 opposite = (tag) -> if tag == @a then @b else @a
113
114 -- when node enters namespace, add default tag
115 @hook 'add', { any, @ns }, (node) =>
116 add_tag node, @a
117
118 -- when node leaves namespace, remove tags
119 @hook 'rmv', { any, @ns }, (node) =>
120 rmv_tag node, @a
121 rmv_tag node, @b
122
123 -- when a is added, remove b and vice-versa
124 @hook 'add', { namespaced, either }, (node, tag) =>
125 rmv_tag node, opposite tag
126
127 -- when a is removed, add b and vice-versa
128 @hook 'rmv', { namespaced, either }, (node, tag) =>
129 add_tag node, opposite tag
130
131 {
132 :Node,
133 :Rule,
134
135 :any,
136 :literal,
137 :oneof,
138 :has,
139 :add_tag,
140 :rmv_tag,
141
142 :Hierarchy,
143 :Toggle,
144 :NamespacedToggle,
145 }
+0
-92
root/experiments/tags/text$moonscript -> fn -> mmm$component.moon less more
0 =>
1 assert MODE == 'CLIENT', '[nossr]'
2
3 import add_tag, rmv_tag, Node, Hierarchy, Toggle, NamespacedToggle from @get 'tags: table'
4 import ReactiveVar, tohtml, text, elements from require 'mmm.component'
5 import article, div, form, span, h3, a, input, textarea, button from elements
6
7 clone = (set) ->
8 assert set and 'table' == (type set), 'not a set'
9 { k,v for k,v in pairs set }
10
11 set_append = (val) -> (set) ->
12 with copy = clone set
13 copy[val] = val
14
15 set_remove = (val) -> (set) ->
16 with copy = clone set
17 copy[val] = nil
18
19 set_join = (tbl, sep=' ') ->
20 ret = ''
21 for tag in pairs tbl
22 ret ..= (tostring tag) .. sep
23 ret
24
25 entries = div!
26
27 class ReactiveNode extends Node
28 new: (...) =>
29 super ...
30 @tags = ReactiveVar @tags
31 @_node = div {
32 span @name, style: { 'font-weight': 'bold' },
33 @tags\map (tags) -> with div!
34 for tag,_ in pairs tags
35 \append a (text tag), href: '#', style: {
36 display: 'inline-block',
37 margin: '0 5px',
38 }
39 }
40
41 @node = tohtml @_node
42
43 has: (tag) => @tags\get![tag]
44 add: (tag) => @tags\transform set_append tag
45 rmv: (tag) => @tags\transform set_remove tag
46
47 rules = {
48 Hierarchy 'home', 'sol'
49 Hierarchy 'sol', 'desktop'
50 Hierarchy 'desktop', 'vacation'
51 Hierarchy 'desktop', 'documents'
52 NamespacedToggle 'documents', 'work', 'personal'
53 -- Toggle 'work', 'personal'
54 -- Hierarchy 'documents', 'work'
55 -- Hierarchy 'documents', 'personal'
56 }
57
58 pictures = for i=1,10
59 with node = ReactiveNode "picture#{i}.jpg"
60 entries\append node
61 add_tag node, 'vacation'
62
63 pers = ReactiveNode 'mypersonalfile.doc'
64 entries\append pers
65
66 article entries, div do
67 yield = coroutine.yield
68 step = coroutine.wrap ->
69 yield "mark document"
70 add_tag pers, 'documents'
71
72 yield "mark personal"
73 add_tag pers, 'personal'
74
75 yield "mark work"
76 add_tag pers, 'work'
77
78 yield "unmark work"
79 rmv_tag pers, 'work'
80
81 yield "remove from documents"
82 rmv_tag pers, 'documents'
83
84 yield false
85
86 next_step = ReactiveVar step!
87 next_step\map (desc) ->
88 if desc
89 button (text desc), onclick: (e) => next_step\set step!
90 else
91 text ''
+0
-11
root/experiments/text$moonscript -> fn -> mmm$dom.moon less more
0 import div, h3, ul, li from require 'mmm.dom'
1 import link_to from (require 'mmm.mmmfs.util') require 'mmm.dom'
2
3 =>
4 div {
5 h3 link_to @
6 ul for child in *@children
7 continue if child\get 'hidden: bool'
8 desc = child\gett 'description: mmm/dom'
9 li (link_to child), ': ', desc
10 }
+0
-1
root/experiments/title: text$plain less more
0 various experiments
+0
-1
root/experiments/torus4d/description: text$plain less more
0 Attempt at rendering a spiral pattern on a 4d meta-torus.
+0
-1
root/experiments/torus4d/link: URL -> git less more
0 https://github.com/s-ol/torus4d
+0
-13
root/games/$order less more
0 the_sacculi
1 zebra_painting
2 gtglg
3 the_monster_within
4 IYNX
5 vision-training-kit
6 curved_curse
7 two_shooting_stars
8 moving_out
9 channel_83
10 plonat_atek
11 lorem_ipsum
12 fake-artist
+0
-6
root/games/IYNX/$order less more
0 cryptex
1 teaser
2 pin_pad
3 pictures
4 ui_demo
5 boot_sequence
+0
-1
root/games/IYNX/boot_sequence/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/817332363273310209
+0
-1
root/games/IYNX/cryptex/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/825476278732148736
+0
-1
root/games/IYNX/date: time$iso8601-date less more
0 2017-01-20
+0
-1
root/games/IYNX/description: text$plain less more
0 a narrative, tangible, physical puzzle incorporating digital elements.
root/games/IYNX/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/IYNX/jam: text$markdown+span less more
0 semester project
+0
-6
root/games/IYNX/pictures/$order less more
0 ui_menu
1 c
2 ui_sample
3 d
4 a
5 b
root/games/IYNX/pictures/a/image$jpeg.jpg less more
Binary diff not shown
root/games/IYNX/pictures/b/image$jpeg.jpg less more
Binary diff not shown
root/games/IYNX/pictures/c/image$jpeg.jpg less more
Binary diff not shown
root/games/IYNX/pictures/d/image$jpeg.jpg less more
Binary diff not shown
+0
-16
root/games/IYNX/pictures/text$moonscript -> fn -> mmm$dom.moon less more
0 import div from require 'mmm.dom'
1 import embed from (require 'mmm.mmmfs.util') require 'mmm.dom'
2
3 =>
4 images = for child in *@children
5 embed child, nil, nil, wrap: 'raw', style: {
6 height: '15em'
7 margin: '0 .5em'
8 }
9
10 div with images
11 .style = {
12 display: 'flex'
13 overflow: 'auto hidden'
14 height: '15rem'
15 }
root/games/IYNX/pictures/ui_menu/image$jpeg.jpg less more
Binary diff not shown
root/games/IYNX/pictures/ui_sample/image$jpeg.jpg less more
Binary diff not shown
+0
-1
root/games/IYNX/pin_pad/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/818230279269679104
+0
-1
root/games/IYNX/teaser/URL -> youtube$video less more
0 https://www.youtube.com/watch?v=i7D_9P2semQ
+0
-66
root/games/IYNX/text$markdown.md less more
0 IYNX
1 ====
2 <mmm-embed path="pictures" nolink></mmm-embed>
3
4 An engaging, tangible and physical electronic puzzle where a mysterious device is found with no indication of its purpose,
5 and alongside it is a personality chip owned by a man named John.
6 The device looks tampered with, as if someone has tried to sabotage or access what it contains.
7 Who’s John and what’s inside?
8
9 You only need to figure a way in to find out.
10
11 <mmm-embed path="teaser" nolink inline>
12 a project teaser
13 </mmm-embed>
14
15 Concept
16 -------
17 IYNX is a physical object in the form of a cube, surrounded by mechanical and digital puzzles.
18 The Player is given the object with no explanation and encouraged to experiment.
19 The game consists of a set of puzzles, each unlocking new content accessible from the screen, slowly fleshing a narrative around them.
20
21 As the player progresses and solves the game puzzle by puzzle,
22 it becomes increasingly clear that the AI that is 'trapped' in the cube is malicious and highly manipulative.
23 Lore that can be pieced together by attentive players suggests
24 that the cube has been built especially to contain the AI in an electronic prison of sorts.
25 It is continuously hinted at, and finally revealed, that every puzzle solved was in fact a security
26 mechanism the player disabled, following the AIs suggestions and instructions.
27 At the end of the game the AI escapes by uploading itself into the internet.
28 Initially the player is lead to believe that he is impersonating the original user of the AI,
29 but it turns out that the AI knew this since the beginning and used the player's curiosity to its own advantage.
30
31 Technical Realisation
32 ---------------------
33 The cube is powered by a Raspberry Pi 3 and two Arduino Micros.
34 The Arduinos are connected as Serial devices.
35
36 The Raspberry Pi is connected to a Touchscreen Panel as well as USB Speakers.
37 It runs a custom electron app that interfaces with the Serial ports,
38 plays back video and audio files and displays a futuristic OS that lets you browse a filesystem.
39
40 <mmm-embed path="ui_demo" nolink>
41 the User Interface was built using react and electron
42 </mmm-embed>
43
44 The game consists of several smaller puzzle components that are arranged to form a story as a whole,
45 through which the player is guided by the 'AI' that posseses the artifact.
46
47
48 <div style="display: flex; flex-wrap: wrap; align-items: flex-start;">
49 <mmm-embed path="boot_sequence" nolink inline>
50 a fake boot sequence for a component of the cube
51 </mmm-embed>
52 <mmm-embed path="pin_pad" nolink inline>
53 a pinpad that grants access to the higher systems of the cube
54 </mmm-embed>
55 <mmm-embed path="cryptex" nolink inline>
56 an early prototype of the Cryptex puzzle that marks the end of the game
57 </mmm-embed>
58 </div>
59
60 Credits
61 -------
62 - Trent Davies: Puzzle and Narrative Design
63 - Sol Bekic: Programming and Electronics
64 - Dominique Bodden: Art and Physical Construction
65 - Ilke Karademir: Puzzle and Graphic Design
+0
-1
root/games/IYNX/title: text$plain less more
0 IYNX
+0
-1
root/games/IYNX/ui_demo/URL -> twitter$tweet less more
0 https://twitter.com/S0lll0s/status/825864142116491264
+0
-1
root/games/channel_83/date: time$iso8601-date less more
0 2017-08-30
+0
-1
root/games/channel_83/description: text$plain less more
0 a low-fi raycasting shooter.
root/games/channel_83/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/channel_83/jam: text$markdown+span less more
0 [Ludum Dare 36](http://ludumdare.com/compo/ludum-dare-36/?action=preview&uid=28620)
+0
-1
root/games/channel_83/link: URL -> itchio$game less more
0 https://s-ol.itch.io/channel-83
+0
-1
root/games/channel_83/title: text$plain less more
0 Channel 83: a last-gen entertainment experience
+0
-1
root/games/curved_curse/date: time$iso8601-date less more
0 2015-04-20
+0
-1
root/games/curved_curse/description: text$plain less more
0 a dungeon shooter with an unconventional gun.
root/games/curved_curse/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/curved_curse/jam: text$markdown+span less more
0 [Ludum Dare 32](http://ludumdare.com/compo/ludum-dare-32/?action=preview&uid=28620)
+0
-1
root/games/curved_curse/link: URL -> itchio$game less more
0 https://s-ol.itch.io/curved-curse
+0
-1
root/games/curved_curse/title: text$plain less more
0 Curved Curse
+0
-1
root/games/fake-artist/date: time$iso8601-date less more
0 2020-04-22
+0
-1
root/games/fake-artist/description: text$plain less more
0 quarantine implementation of a bluffing-drawing game.
+0
-124
root/games/fake-artist/text$html.html less more
0 <!DOCTYPE html>
1 <html>
2 <head>
3 <script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/3.0.5/seedrandom.min.js"></script>
4 <style>
5 html {
6 margin: 0;
7 padding: 0;
8 background: #272727;
9
10 display: flex;
11 min-height: 100vh;
12 align-items: center;
13 overflow-y: auto;
14 }
15
16 body {
17 font-family: sans-serif;
18
19 width: 12em;
20 margin: 1em auto;
21 border: 4px solid #696969;
22 border-radius: 0.5rem;
23 background: #eeeeee;
24 overflow: hidden;
25 }
26
27 header {
28 padding: .5rem;
29 background: #696969;
30 color: #eeeeee;
31 }
32
33 h2 {
34 margin: 0;
35 }
36
37 #main, #setup {
38 padding: .5rem;
39 }
40
41 main .inner {
42 margin-bottom: .5em;
43 }
44
45 main .inner {
46 overflow: hidden;
47 max-height: 12em;
48
49 transition: all 0.3s;
50 }
51
52 main .inner > div {
53 display: flex;
54 }
55 main .inner > div > input {
56 flex: 1;
57 width: 0;
58 margin-left: 1em;
59 }
60
61 #setup {
62 display: none;
63 }
64 .setup #setup {
65 display: block;
66 }
67
68 .sensitive {
69 position: relative;
70
71 padding: 0.3em;
72 border: 0.2em solid #696969;
73 }
74
75 .sensitive .cover {
76 position: absolute;
77 top: 0;
78 left: 0;
79 right: 0;
80 bottom: 0;
81 padding: 0.5em;
82 color: #eeeeee;
83 background: #696969;
84 transition: opacity 300ms;
85 }
86
87 .sensitive:hover .cover {
88 opacity: 0;
89 pointer-events: none;
90 }
91
92 pre {
93 font-size: 2em;
94 font-weight: bold;
95 margin: 0;
96 }
97 pre.spy {
98 color: #c33;
99 }
100
101 a { color: inherit; }
102 </style>
103 </head>
104 <body>
105 <main>
106 <header>
107 <h2>Fake Artist</h2>
108 <div id="head"></div>
109 </header>
110 <div id="setup" class="inner">
111 <div>spies: <input id="spies" type="number" value="1" /></div>
112 <div>players:</div>
113 <ul id="players"></ul>
114 <div>
115 <button id="add">add</button>
116 <input id="name" type="text" />
117 </div>
118 </div>
119 <div id="main"></div>
120 </main>
121 <script src=":text/javascript"></script>
122 </body>
123 </html>
+0
-368
root/games/fake-artist/text$javascript.js less more
0 const words = [
1 "airplane",
2 "alive",
3 "alligator",
4 "angel",
5 "ant",
6 "apple",
7 "arm",
8 "baby",
9 "backpack",
10 "ball",
11 "balloon",
12 "banana",
13 "bark",
14 "baseball",
15 "basketball",
16 "bat",
17 "bathroom",
18 "beach",
19 "beak",
20 "bear",
21 "bed",
22 "bee",
23 "bell",
24 "bench",
25 "bike",
26 "bird",
27 "blanket",
28 "blocks",
29 "boat",
30 "bone",
31 "book",
32 "bounce",
33 "bow",
34 "bowl",
35 "box",
36 "boy",
37 "bracelet",
38 "branch",
39 "bread",
40 "bridge",
41 "broom",
42 "bug",
43 "bumblebee",
44 "bunk bed",
45 "bunny",
46 "bus",
47 "butterfly",
48 "button",
49 "camera",
50 "candle",
51 "candy",
52 "car",
53 "carrot",
54 "cat",
55 "caterpillar",
56 "chair",
57 "cheese",
58 "cherry",
59 "chicken",
60 "chimney",
61 "clock",
62 "cloud",
63 "coat",
64 "coin",
65 "comb",
66 "computer",
67 "cookie",
68 "corn",
69 "cow",
70 "crab",
71 "crack",
72 "crayon",
73 "cube",
74 "cup",
75 "cupcake",
76 "curl",
77 "daisy",
78 "desk",
79 "diamond",
80 "dinosaur",
81 "dog",
82 "doll",
83 "door",
84 "dragon",
85 "dream",
86 "drum",
87 "duck",
88 "ear",
89 "ears",
90 "Earth",
91 "egg",
92 "elephant",
93 "eye",
94 "eyes",
95 "face",
96 "family",
97 "feather",
98 "feet",
99 "finger",
100 "fire",
101 "fish",
102 "flag",
103 "float",
104 "flower",
105 "fly",
106 "football",
107 "fork",
108 "frog",
109 "ghost",
110 "giraffe",
111 "girl",
112 "glasses",
113 "grapes",
114 "grass",
115 "hair",
116 "hamburger",
117 "hand",
118 "hat",
119 "head",
120 "heart",
121 "helicopter",
122 "hippo",
123 "hook",
124 "horse",
125 "house",
126 "ice cream cone",
127 "inchworm",
128 "island",
129 "jacket",
130 "jail",
131 "jar",
132 "jellyfish",
133 "key",
134 "king",
135 "kite",
136 "kitten",
137 "knee",
138 "ladybug",
139 "lamp",
140 "leaf",
141 "leg",
142 "legs",
143 "lemon",
144 "light",
145 "line",
146 "lion",
147 "lips",
148 "lizard",
149 "lollipop",
150 "love",
151 "man",
152 "Mickey Mouse",
153 "milk",
154 "mitten",
155 "monkey",
156 "monster",
157 "moon",
158 "motorcycle",
159 "mountain",
160 "mountains",
161 "mouse",
162 "mouth",
163 "music",
164 "nail",
165 "neck",
166 "night",
167 "nose",
168 "ocean",
169 "octopus",
170 "orange",
171 "oval",
172 "owl",
173 "pants",
174 "pen",
175 "pencil",
176 "person",
177 "pie",
178 "pig",
179 "pillow",
180 "pizza",
181 "plant",
182 "popsicle",
183 "purse",
184 "rabbit",
185 "rain",
186 "rainbow",
187 "ring",
188 "river",
189 "robot",
190 "rock",
191 "rocket",
192 "sea",
193 "seashell",
194 "sheep",
195 "ship",
196 "shirt",
197 "shoe",
198 "skateboard",
199 "slide",
200 "smile",
201 "snail",
202 "snake",
203 "snowflake",
204 "snowman",
205 "socks",
206 "spider",
207 "spider web",
208 "spoon",
209 "stairs",
210 "star",
211 "starfish",
212 "suitcase",
213 "sun",
214 "sunglasses",
215 "swimming pool",
216 "swing",
217 "table",
218 "tail",
219 "train",
220 "tree",
221 "truck",
222 "turtle",
223 "water",
224 "whale",
225 "wheel",
226 "window",
227 "woman",
228 "worm",
229 "zebra",
230 "zoo",
231 ];
232
233 const main = document.getElementById('main');
234 const head = document.getElementById('head');
235 const players = document.getElementById('players');
236 const spies = document.getElementById('spies');
237 const name = document.getElementById('name');
238 const add = document.getElementById('add');
239
240 const shuffle = (array, rng) => {
241 for (let i = array.length - 1; i > 0; i--) {
242 let j = Math.floor(rng() * (i + 1));
243 [array[i], array[j]] = [array[j], array[i]];
244 }
245 };
246
247 const infect = a => {
248 a.onclick = () => {
249 const hash = a.href.split('#')[1];
250 update('#' + hash);
251 };
252 };
253
254 const genSeed = rng => (
255 words[Math.floor(rng() * words.length)].replace(' ', '-').toLowerCase()
256 + '_' + words[Math.floor(rng() * words.length)].replace(' ', '-').toLowerCase()
257 + '_' + words[Math.floor(rng() * words.length)].replace(' ', '-').toLowerCase()
258 );
259
260 const update = (hash) => {
261 const [ mode, seed, n, ...names ] = hash.split(/,/);
262 const rng = new Math.seedrandom(seed);
263 const nextSeed = genSeed(rng);
264
265 while (main.lastChild)
266 main.removeChild(main.lastChild);
267
268 while (head.lastChild)
269 head.removeChild(head.lastChild);
270
271 if (mode === '#link') {
272 const code = document.createElement('code');
273 code.innerText = seed;
274 head.append(code);
275
276 document.body.className = '';
277 names.forEach((name, i) => {
278 const a = document.createElement('a');
279 a.innerText = 'play';
280 a.href = `#play,${seed},${n},${names.join(',')},${i}`;
281 infect(a);
282
283 const div = document.createElement('div');
284 div.append(`${name}: `);
285 div.append(a);
286 main.append(div);
287 });
288
289 const a = document.createElement('a');
290 a.innerText = 'next round';
291 a.href = `#link,${nextSeed},${n},${names.join(',')}`;
292 infect(a);
293 main.append(document.createElement('br'));
294 main.append(a);
295 } else if (mode === '#play') {
296 const code = document.createElement('code');
297 code.innerText = seed;
298 head.append(code);
299
300 document.body.className = '';
301 const i = names.pop();
302
303 const word = words[Math.floor(rng() * words.length)];
304 const list = names.map((x, i) => i < +n);
305 shuffle(list, rng);
306
307 const div = document.createElement('div');
308 div.className = 'sensitive';
309
310 if (list[+i]) {
311 const span = document.createElement('pre');
312 span.className = 'spy';
313 span.innerText = 'SPY!';
314 div.append("you are a");
315 div.append(document.createElement('br'));
316 div.append(span);
317 } else {
318 const span = document.createElement('pre');
319 span.innerText = word;
320 div.append("the word is");
321 div.append(document.createElement('br'));
322 div.append(span);
323 }
324
325 const cover = document.createElement('div');
326 cover.className = 'cover';
327 cover.innerText = '(hover here)';
328 cover.innerText = `${names[i]}, please hover here to read.`;
329 div.append(cover);
330
331 const a = document.createElement('a');
332 a.innerText = 'next round';
333 a.href = `#play,${nextSeed},${n},${names.join(',')},${i}`;
334 infect(a);
335
336 main.append(div);
337 main.append(document.createElement('br'));
338 main.append(a);
339 } else {
340 document.body.className = 'setup';
341 const seed = genSeed(Math.random);
342 const names = [];
343 const a = document.createElement('a');
344 a.innerText = 'start';
345 a.href = `#link,${seed},${spies.value},${names.join(',')}`;
346 main.append(a);
347 infect(a);
348
349 add.onclick = () => {
350 if (!name.value)
351 return;
352
353 const li = document.createElement('li');
354 li.innerText = name.value;
355 players.append(li);
356 names.push(name.value);
357 a.href = `#link,${seed},${spies.value},${names.join(',')}`;
358 name.value = '';
359 };
360
361 spies.onchange = () => {
362 a.href = `#link,${seed},${spies.value},${names.join(',')}`;
363 }
364 }
365 }
366
367 update(window.location.hash);
+0
-1
root/games/fake-artist/title: text$plain less more
0 Fake Artist
+0
-1
root/games/gtglg/date: time$iso8601-date less more
0 2014-12-23
+0
-1
root/games/gtglg/description: text$plain less more
0 a slightly psychedelic physics puzzler with gary, a green-legged giraffe.
root/games/gtglg/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/gtglg/link: URL less more
0 https://s-ol.itch.io/gary-the-green-legged-giraffe
+0
-1
root/games/gtglg/title: text$plain less more
0 Gary, the green-legged Giraffe
+0
-1
root/games/lorem_ipsum/date: time$iso8601-date less more
0 2016-09-01
+0
-1
root/games/lorem_ipsum/description: text$markdown+span less more
0 a labyrinth game concering medialisation and multiple viewpoints. developed with the [ForChange research alliance](http://www.forchange.de/ergebnisse/resilienzspiele/).
root/games/lorem_ipsum/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/lorem_ipsum/jam: text$plain less more
0 GamesForChange
+0
-1
root/games/lorem_ipsum/link: URL less more
0 https://loremipsum.s-ol.nu/
+0
-1
root/games/lorem_ipsum/title: text$plain less more
0 Lorem Ipsum
+0
-1
root/games/moving_out/date: time$iso8601-date less more
0 2016-12-11
+0
-1
root/games/moving_out/description: text$plain less more
0 a QWOP-y platformer in which you play a room.
root/games/moving_out/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/moving_out/jam: text$markdown+span less more
0 [Ludum Dare 37](http://ludumdare.com/compo/ludum-dare-37/?action=preview&uid=28620)
+0
-1
root/games/moving_out/link: URL -> itchio$game less more
0 https://s-ol.itch.io/moving-out
+0
-1
root/games/moving_out/title: text$plain less more
0 Moving Out
+0
-3
root/games/plonat_atek/$order less more
0 itch_page
1 video
2 pictures
+0
-1
root/games/plonat_atek/date: time$iso8601-date less more
0 2017-04-25
+0
-1
root/games/plonat_atek/description: text$plain less more
0 a sound-only breakout game, displayable on an oscilloscope and realized in the PureData visual programming environment.
root/games/plonat_atek/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/plonat_atek/itch_page/URL -> itchio$game less more
0 https://s-ol.itch.io/plonat-atek
+0
-1
root/games/plonat_atek/jam: text$markdown+span less more
0 [Ludum Dare 38](https://ldjam.com/events/ludum-dare/38/plonat-atek)
+0
-3
root/games/plonat_atek/pictures/$order less more
0 setup
1 amaze
2 mockup
+0
-1
root/games/plonat_atek/pictures/amaze/attribution: text$markdown+span less more
0 photo by [Rick Hoppmann](https://twitter.com/tinyruin)
root/games/plonat_atek/pictures/amaze/image$jpeg.jpg less more
Binary diff not shown
root/games/plonat_atek/pictures/mockup/image$jpeg.jpg less more
Binary diff not shown
root/games/plonat_atek/pictures/setup/image$jpeg.jpg less more
Binary diff not shown
+0
-18
root/games/plonat_atek/pictures/text$moonscript -> fn -> mmm$dom.moon less more
0 import div from require 'mmm.dom'
1 import embed from (require 'mmm.mmmfs.util') require 'mmm.dom'
2
3 =>
4 images = for child in *@children
5 embed child, nil, nil, attr: {
6 style: {
7 height: '15em'
8 margin: '0 .5em'
9 flex: '1 0 auto'
10 }
11 }
12
13 div with images
14 .style = {
15 display: 'flex'
16 overflow: 'auto hidden'
17 }
+0
-57
root/games/plonat_atek/text$markdown.md less more
0 # <mmm-embed nolink facet="title"></mmm-embed>
1
2 <mmm-embed nolink path="pictures"></mmm-embed>
3
4 *Plonat Atek* is a digital game that communicates itself to the player only via the stereo headphone jack.
5 The signal is split, and one of the streams is fed into a pair of headphones that show the game to the player as a melody,
6 interlaced with a series of blips and bursts of noise.
7 The other stream leads into an oscilloscope, that visualises the two channels by moving a dot across the screen in correspondence to the signal.
8 On the screen, the game manifests as a ball bouncing around in a circular version of *Breakout*.
9
10 Both the oscilloscope and the speakers are analog devices that interpret the signal and generate a representation of the game.
11 The representations are based on interrelated properties of the same signal.
12 Thus, the relationship between the audio and visual components is not merely designed, rather the two emerge from identical information:
13 In *Plonat Atek*, the sound is designed to be seen and the visuals are designed to be heard.
14 For example the blip heard when the ball bounces is the distortion seen in the same moment,
15 and the noise heard when the ball is lost is visible as a glitch on the oscilloscope.
16
17 <mmm-embed nolink path="video"></mmm-embed>
18
19 ## Awards and Exhibitions
20 *Plonat Atek* was awarded first place in the *Innovation* category, 8th in *Audio* and 24th overall at the *Ludum Dare 38 Compo* in 2017.
21 The project has been exhibited at *A MAZE. Berlin 2018* and *Maker Faire Rome 2019*.
22
23 ## History
24 *Plonat Atek* was originally developed in 48 hours for the *Ludum Dare 38 Compo* in June 2017.
25 You can find the original submission [here][ld] and documentation on how to download and run the jam version on the [itch.io page][itch].
26
27 Articles on the game accompanied by the video recording above have been published by [Hackaday][hackaday], [VICE Motherboard][motherboard] and others.
28
29 The jam version is designed to run on an end-user Computer and is played using the keyboard.
30 In early 2018 I decided to build a self-contained hardware version that is played using a rotary knob.
31 This is the version pictured above.
32
33 ## Artist Statement
34 *Plonat Atek* is an exploration into the unification of audiovisual signals in the context of feedback in interactive systems.
35 Both digital and analog audio signals are very thin encodings,
36 as they map directly to the vibrations on our tympana and thereby the sensations they represent.
37 In contrast, raster video signals or image encodings are a very contrived, optimized and complicated;
38 the discrete and serial pixel-per-pixel description of shapes does not come close at all to the way our human perception works.
39 This is also obvious as the technology required to decode a video signal or image into something a human can perceive is extremely complex.
40 In *Plonat Atek*, the visual output on the oscilloscope is as directly based on the waveforms encoding it as the audio is.
41 The thinner encoding allows the simultaneous use of the exact same signal for both the audio output on the speakers and
42 the visual output on the oscilloscope, thereby intrinsically connecting the two.
43
44 At the same time Plonat Atek is a media archaeological project and hommage.
45 The earliest video games, such as *Tennis for Two* and *Spacewar!*, as well as later arcade games like *Asteroids*,
46 and even consoles like the *Vectrex* share the CRT screen whose warm glow transports a unique feeling of messy analogue-ness
47 coupled with a homely sense of healthy imperfection.
48 The game itself is a reinterpretation of the classic *Breakout*, a game many players may recognize nostalgically and
49 that has already gone through transformations from hardware to software, and from an arcade to a pc and finally a mobile game
50 or something found on a children’s toy that comes free with an order at a fast-food restaurant.
51
52 [ld]: https://ldjam.com/events/ludum-dare/38/plonat-atek
53 [itch]: https://s-ol.itch.io/plonat-atek
54
55 [hackaday]: https://hackaday.com/2017/11/05/programming-an-oscilloscope-breakout-game-in-pure-data/
56 [motherboard]: https://motherboard.vice.com/en_us/article/59yw9z/watch-this-awesome-oscilloscope-breakout-game
+0
-1
root/games/plonat_atek/title: text$plain less more
0 Plonat Atek
+0
-1
root/games/plonat_atek/video/URL -> youtube$video less more
0 https://www.youtube.com/watch?v=SIQAk9_nc-s
+0
-45
root/games/text$moonscript -> fn -> mmm$dom.moon less more
0 import div, span, h3, ul, li, a, h4, img, p from require 'mmm.dom'
1 import link_to from (require 'mmm.mmmfs.util') require 'mmm.dom'
2 import ropairs from require 'mmm.ordered'
3
4 =>
5 div {
6 h3 link_to @
7 ul do
8 games = { (p\gett 'date: time/unix'), p for p in *@children }
9
10 children = for k, child in ropairs games
11 desc = child\gett 'description: mmm/dom'
12 jam = if link = child\get 'jam: mmm/dom'
13 span '[', link, ']', style: float: 'right', clear: 'right', color: 'var(--gray-dark)'
14
15 li (link_to child), ': ', desc, jam
16
17 children
18 -- ul with for child in *@children
19 -- link_if_content = (opts) ->
20 -- a with opts
21 -- if true or child\find 'mmm/dom'
22 -- .style = { 'text-decoration': 'none' }
23 -- .href = child.path
24 -- .onclick
25 --
26 -- li link_if_content {
27 -- h4 {
28 -- style: { 'margin-bottom': 0 }
29 -- (child\get 'title: mmm/dom') or child\gett 'name: alpha'
30 -- }
31 -- div {
32 -- -- style: {
33 -- -- display: 'flex'
34 -- -- 'justify-content': 'space-around'
35 -- -- }
36 -- -- img src: child\gett 'icon: URL -> image/.*'
37 -- p (child\gett 'description: mmm/dom'), style: { 'flex': '1 0 0', margin: '1em' }
38 -- }
39 -- }
40 --
41 -- .style = {
42 -- 'list-style': 'none'
43 -- }
44 }
+0
-1
root/games/the_monster_within/date: time$iso8601-date less more
0 2015-08-25
+0
-1
root/games/the_monster_within/description: text$plain less more
0 a top down action brawler with a twist.
root/games/the_monster_within/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/the_monster_within/jam: text$markdown+span less more
0 [Ludum Dare 33](http://ludumdare.com/compo/ludum-dare-33/?action=preview&uid=28620)
+0
-1
root/games/the_monster_within/link: URL -> itchio$game less more
0 https://s-ol.itch.io/the-monster-within
+0
-1
root/games/the_monster_within/title: text$plain less more
0 The Monster Within
+0
-1
root/games/the_sacculi/date: time$iso8601-date less more
0 2018-08-14
+0
-1
root/games/the_sacculi/description: text$markdown+span less more
0 a series of Point-and-Click minigames with a common structure.
+0
-1
root/games/the_sacculi/jam: text$markdown+span less more
0 [Ludum Dare 42](https://ldjam.com/events/ludum-dare/42/sacculos-the-game)
+0
-1
root/games/the_sacculi/link: URL -> itchio$collection less more
0 https://itch.io/c/367008/the-sacculi
+0
-1
root/games/the_sacculi/title: text$plain less more
0 The Sacculos Saga
+0
-1
root/games/title: text$plain less more
0 games
+0
-1
root/games/two_shooting_stars/date: time$iso8601-date less more
0 2016-06-01
+0
-1
root/games/two_shooting_stars/description: text$plain less more
0 a narrative point-and-click adventure.
root/games/two_shooting_stars/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/two_shooting_stars/jam: text$markdown+span less more
0 semester project
+0
-1
root/games/two_shooting_stars/link: URL less more
0 http://www.colognegamelab.de/studentprojects/i-looked-at-the-sky-and-saw-two-shooting-stars-but-couldnt-come-up-with-a-wish-2016/
+0
-1
root/games/two_shooting_stars/title: text$plain less more
0 I looked at the sky and saw two shooting stars but couldn't come up with a wish
+0
-1
root/games/vision-training-kit/date: time$iso8601-date less more
0 2016-08-05
+0
-1
root/games/vision-training-kit/description: text$plain less more
0 a puzzle game based on a famicase cartridge design.
root/games/vision-training-kit/icon: image$png.png less more
Binary diff not shown
+0
-1
root/games/vision-training-kit/jam: text$markdown+span less more
0 [AGBIC 2016](https://itch.io/jam/a-game-by-its-cover-2016)
+0
-1
root/games/vision-training-kit/link: URL -> itchio$game less more
0 https://s-ol.itch.io/vision-training-kit
+0
-1
root/games/vision-training-kit/title: text$plain less more
0 視能訓練キット | vision-training-kit
+0
-1
root/games/zebra_painting/date: time$iso8601-date less more
0 2018-09-13
+0
-1
root/games/zebra_painting/description: text$plain less more
0 a small reaction/dexteriy game about painting zebras.
+0
-1
root/games/zebra_painting/jam: text$markdown+span less more
0 [AGBIC 2018](https://itch.io/jam/a-game-by-its-cover-2018)
+0
-1
root/games/zebra_painting/link: URL -> itchio$game less more
0 https://s-ol.itch.io/zebra-painting
+0
-1
root/games/zebra_painting/title: text$plain less more
0 Zebra Painting
33 => div {
44 style: { 'max-width': '700px' }
55 h3 link_to @
6 p "mmm is a collection of Lua/Moonscript modules for web development.
7 All modules are 'polymorphic' - they can run in the ", (i 'browser'),
6 p "mmm.dom and mmm.component are Lua/Moonscript modules for web development.
7 Both modules are 'polymorphic' - they can run in the ", (i 'browser'),
88 ", using the native browser API for creating and interacting with DOM content, as well as on the ",
99 (i 'server'), ", where they operate on and produce equivalent HTML strings."
1010 p "As the two implementations of each module are designed to be compatible,
root/portfolio/1u-mod/media: image$jpeg.jpg less more
Binary diff not shown
root/portfolio/VJmidiKit/media: video$mp4.mp4 less more
Binary diff not shown
+0
-1
root/portfolio/_web_view: type