Commit fc49d512 authored by Jake Read's avatar Jake Read
Browse files

move doc

parents
# Machine for Playing Music for Pieces of Wood by Steve Reich by Jake Read
![video](video/reich-v01-web.mp4)
I've been really stoked by Steve Riech's music lately. This piece came out as a really fun project:
<!-- blank line -->
<figure class="video_container">
<iframe src="https://www.youtube.com/embed/gy2kyRrXm2g" frameborder="0" allowfullscreen="true"> </iframe>
</figure>
<!-- blank line -->
[Music for Pieces of Wood](https://www.youtube.com/watch?v=gy2kyRrXm2g)
Reich is obsessed with polyrhythms... [this is also fun](https://www.youtube.com/watch?v=i36Qhn7NhoA), and [this is wonderful](https://www.youtube.com/watch?v=PMsYuFrKUQ8).
To bring up network motion control (although a really awesome version of this uses virtual impedence control for bldc motors, I'm using stepper because they work already), I want to write a small sequencer program in mods that sends drum-ticks to a set of networked motors.
![wb-sequencer](images/wb-sequencer.jpg)
Here's my sketch for how I'll do this mods. To 'sequence' over periods, I need a delay module so that I can insert periods between looping events. Then I use a 'seqencer' which, on each event, reads a bit pattern to either fire-or-not-fire an output. I have one loop for firing sequences, and another for firing sub-sequences. They have nested periods. Then I use a 'step sequencer' which, on any event, pushes a sequence of steps to a motor. In this case the sequence of steps defines one mallet strike. The stepper motor module (the bit that will later be replaced with some elegant hardware:software representation) formats these moves into packets that will make their way over the network to a motor, which will execute those moves.
To do the business, I'll get 5 motors up and running. Each of those will have a mallet on a flexure (to 'hit' but not rest on the key) and will strike a key. I can 3DP most of the finniky mechanical bits, including the flexure, and I'll cut the mounting equipment on a laser, or the mill, out of plywood. I can make the keys maybe out of CNC'd plywood, or if I can find some solid hardwood stock, out of that. A nicely bounded project!
## Mods
I wrote a few mods (a sequencer, a step sequencer, a stepper abstraction, an event gate, and I added some counting to the delay module).
I have one 'sequencer' stage set up:
![mods-onesequencer](images/one-sequencer.png)
Now I'll just add an address field to that stepper module, and then I can plug that into the serial server. I'll be sending steps, in sequence, to my motor. Hurray!
As for the embedded code, I have most of that documented [here](https://gitlab.cba.mit.edu/jakeread/atkstepper17). But I need to write the application side of APA - the bit that will actually take key:value bytes, bit shift them into words (sometimes) and execute commands - in my setup, that will mean just updating values in a state machine as soon as they arrive. So I can remotely change the stepper's target position and speed, and the active statemachine will work with new values once they arrive.
## Keys
[Vic Firth has a blog post about the piece](http://vicfirth.com/reich-music-for-pieces-of-wood/) so I've learned that the keys are an A, B, C-sharp, D-sharp and a D-sharp one octave up. Music! I'll need to learn how to tune (or design) the size of my keys, and then I'll need a way to lay them out.
The shortest period is also quite short... er, fast. I'm a bit worried my little steppers aren't going to be able to beat the beat quickly enough.
Turns out the best way to do this is to [make one and measure it](https://www.mmdigest.com/Tech/breen_xylo.html), then the rest of the spectrum follows with some simple maths. I found my top-end D# to be about 10 & 7/8 " - with a note of 2489 Hz.
![d](images/d-sharp-spectrum.png)
![d](images/d-sharp.jpg)
From the length of that key, I can ascend the scale by multiplying that length by $`2^(-1/24)`$, or descend by $`2^(1/24)`$. That means I have these keys:
Note | Hz | Length (mm)
---
A | 880.0 | 379.0
B | 987.8 | 357.8
C# | 1108.7 | 337.7
D# | 1244.5 | 318.7
D# up one | 2489.0 | 225.4
## Hardware
I figured out how to do this this morning. Hurray. I've wanted to mount it on a wall but have been at a miss as to how I might suspend the keys properly - of course, that's the trick: I'll just suspend them.
Tom Sachs is coming to town on Saturday, I have a major crush on him... see below 'Objects of Devotion'
![sachs](images/objects-of-devotion.jpg)
So I'm going to do this pegboard style...
![millpattern](images/the-milling.png)
I've got my keys, pegboard and 3DP bits ready to assembly now. Something else [I learned about](http://hyperphysics.phy-astr.gsu.edu/hbase/Music/barres.html) which seems elementary now that it occurs to me, is that I want to pin or suspend the bars at their nodes - for a free bar vibrating in the fundamental mode, the nodes are 0.224 * L from the ends. I'm changing some of my 3DP bits to reflect this.
![free bar](images/freebar.gif)
Put this together:
![assy](images/assembled.jpg)
## Mods: Plumbing
Here's a short video of the system running, this took a lot of footwork in C and some footwork in JavaScript, to write and then route and then act on network packets.
![mods shoothrough](video/mods-shoothrough-dc.mp4)
Inside my mammalian brain, this certainly feels very much lke real time. The Serial Server runs about 0.1us overhead (there and back, looks like 0.2us average ring - although this varies up to 0.5us). Then packets take some time to get through to the motors (they're about 15 bytes each, at 115200 baud, so that's 1ms transmit time, over 2 hops). A note: one of my short order tasks is to use a better Serial Bridge to go to 3MBaud, so this will be more like 40us. If we get [those FPGAs up and running](https://gitlab.cba.mit.edu/jakeread/coclocking) at 60MBaud, that's a 2us packet. Hot fire. In any case, this works now.
## Mods: Composing
Next up is building a deeper understanding of [Music for Pieces of Wood](https://www.allmusic.com/composition/music-for-pieces-of-wood-for-5-pairs-of-tuned-claves-mc0002370366) and trying to invent a fun / weird way to 'write' it with sequencing modules.
This would involve some kind of zen, empty-neuron state, a careful watch & whiteboard of the song, and then some attempt to reduce that to an algorithm / series of building blocks.
OK, Reich's Music for Pieces of Wood is deceptively simple / complex. Imagine Sol Lewitt, but this time he is a drummer.
Keys are descending 1: D# +1 oct, 2: D#, 3: C#, 4: B, 5: A
The piece is broken into three sections, each following the same algorithm.
Starting at 12 Bases,
K1 repeats P101010101010
4 bases pass
K4 plays 4 bases P111011010110
until pattern is full,
every 4 bases,
K5 adds one element of P010110111011
randomly
until pattern is full,
every 4 bases,
K3 adds one element of P010110111011
randomly
until pattern is full,
every 4 bases
K2 adds one element of P111011010110
randomly
K2-5 play 4 bases K4P
K2,3,5 stop, K4 plays 4 bases K4P
Then we truncate the 'input' K1 and K4 patterns, first by 4 (for a base 8 rhythm) and then by 6 for a base 6 rhythm. We repeat the pattern above, adding in K5, K3 and K2 sequentially. Those patterns are basically bit-shifted copies of K4. Forgive my musical ignorance as I trounce about without any of the right nomenclature.
## To the Javascript
![wb](images/wb-mfpw-01.jpg)
![wb](images/wb-mfpw-02.jpg)
![mods](images/mods-mfpw-01.png)
Adding add-on Reich Gates
![mods](images/mods-mfpw-02.png)
This seems to run wonderfully. Now I'll add a final-sync step for the last 4 bars, and then a 'return and truncate' to the metronome. I think I'm done writing the mods, we'll have to see.
OK, here's the final sketch as it ran:
![mods](images/mods-mfpw-03.png)
This was, to say the least, a PITA. There's a temptation with MODS to write everything into one little program - I could have just written one 'Music for Pieces of Wood' mod - but then mods would just be a tool for experts to house their code in, not a tool for beginners to scratch together programs from basic elements. So to do this, I tried breaking the rhythm generation down into bite-size elements that I strung together.
That said, making the bitwise components straightforward and fungible enough to be really universal takes careful thought. There should be effort made to specify and convert for different types of events, and clarity in labelling inputs and outputs. There's a lot of work to do in mods until it can really be approachable, I think.
## Adding State back into MODS
These are notes for me, mostly, to reconstruct the Reich program. All of the mods used have default states that need to have the right initial conditions for the thing to work.
Keys are descending 1: D# +1 oct, 2: D#, 3: C#, 4: B, 5: A
Addresses
K1, D#: 0,4
K4, B: 0,1
K5, A: 0,1,0
K3, C#: 0,2
K2, D#: 0,3
2,0,2,0,2,0,2,0,2,0,2,0
2,2,2,0,2,2,0,2,0,2,2,0
update shifts -> -6, 2, 0
update truncate and startcount sequences
-18,1000 | 100,1000
do 2, 75ms delay, no loop
set delay step gates open
## Debugging
So, this is nice because it means the experiment was worthwhile. First note is that javascript-in-browser is maybe not perfect for 'realtime' control, I suppose no one should be surprised by this. When we're not in the tab, the runtimes drop drastically - so if a user is running mods, they'd better be running mods for as long as their task is active.
What is the speed of console logs?
- [quite slow](https://stackoverflow.com/questions/11426185/will-console-log-reduce-javascript-execution-performance)
There's a way around this: write messages to a variable that we can inspect.
Perhaps the answer for realtime is to run a node server that dishes graph pages: graphs are mirrors of the node server... I like this because we can put the node server on an RPi and put it to sleep when we're not using it. Taking a look at what that architecture would be like could be fun. Alternately, mods and this node-server can play together: some computation is done in browser, some on the server. This is just like adding a new layer.
The next issue is that I'm overrunning the network.
My current network speed is 115200kbps - limited by the FTDI bridge I'm using.
I'm pushing packets on 50ms (at the shortest) intervals. In the worst case, I'm driving 5 packets of speed & position: those packets are *0,1,0,ptr,end,steps,b1,b2,b3,b4,speed,b1,b2,b3,b4* 15 bytes long, so I'm pushing 600 bits down the line, which actually should be well within the limit... 5ms.
There's some variance in the program - sometimes the final beats sync up, other times some seem to miss. This is either (1) some mods events not being handled, or being handled out of order, or (2) some errors on my part in event sequencing, etc. Likely a healthy, confusing mix of both. In any case, I was able to get a semi-passable version of this to work, and that's in the video that I will likely put at the top of this page.
# Notes for Next Time
I want to circle back on this when I've made some more progress on mods and on my stepper motor controls. I want to do this with the hardware / software representation I'm dreaming of. There's some chance that becomes a node server running locally on a raspberry pi (I noticed some behaviour from in-browser mods that I don't want to see on time critical machines).
I would also like to improve the motor controller for weird tasks like this. For example 'hit the thing' rather than 'go to this position at this speed' would be really cool. I think I can do this with encoder input and the raw current value registers on the stepper motor. This is something I may get to, or may not. There's also some chance I eventually do this with brushless motors, but I have some controls flexing to do before I get there.
\ No newline at end of file
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This diff is collapsed.
{"modules":{"0.37807284615053505":{"definition":"//\n// character input output\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n //\n // module globals\n //\n var mod = {}\n //\n // name\n //\n var name = 'stepper'\n //\n // initialization\n //\n var init = function() {\n mod.path = [1, 2] // path on net\n mod.delimiters = [254, 255]\n mod.steps = [129, 0, 0, 0, 240]\n mod.speed = [130, 0, 0, 2, 255]\n mod.packet = []\n }\n //\n // inputs\n //\n var inputs = {\n sync: {\n type: 'event',\n event: function(evt) {\n console.log('stepper triggered')\n outputs.packet.event()\n }\n },\n position: {\n type: 'float, units',\n event: function(evt) {\n console.log('steps input: ')\n console.log(evt.detail)\n mod.steps[1] = (evt.detail >> 24) & 255\n mod.steps[2] = (evt.detail >> 16) & 255\n mod.steps[3] = (evt.detail >> 8) & 255\n mod.steps[4] = evt.detail & 255\n }\n },\n speed: {\n type: 'float, units/s',\n event: function(evt) {\n console.log('speed input: ')\n console.log(evt.detail)\n mod.speed[1] = (evt.detail >> 24) & 255\n mod.speed[2] = (evt.detail >> 16) & 255\n mod.speed[3] = (evt.detail >> 8) & 255\n mod.speed[4] = evt.detail & 255\n }\n }\n }\n //\n // outputs\n //\n var outputs = {\n packet: {\n type: 'packet',\n event: function(something) {\n /*\n apa_end_addr_delimiter 255\n apa_addr_pointer 254\n apa_addr_flood 253\n\n steps: 127\n speed: 128\n\n uint8_t: 0\n int8_t: 1\n uint32_t: 2\n int32_t: 3\n float: 4\n double: 8\n */\n mod.packet = [] // clear\n mod.packet = mod.packet.concat(mod.path, mod.delimiters, mod.steps, mod.speed);\n mod.packet.splice(0, 0, mod.packet.length + 1)\n console.log(mod.packet);\n mods.output(mod, 'packet', mod.packet)\n }\n }\n }\n //\n // interface\n //\n var interface = function(div) {\n mod.div = div\n\n div.appendChild(document.createTextNode('path to stepper:'))\n div.appendChild(document.createElement('br'))\n var addrIn = document.createElement('input')\n addrIn.type = 'text'\n addrIn.size = mods.ui.cols\n addrIn.addEventListener('change', function() {\n mod.path = addrIn.value.split(',')\n if(mod.path[0] == ''){\n mod.path = []\n console.log('zero length')\n } else {\n for(var i = 0; i < mod.path.length; i ++){\n mod.path[i] = parseFloat(mod.path[i])\n }\n }\n // trigger func that looks at mod.in[]s and checks length, builds array of arrays?\n console.log('new path: ', mod.path)\n })\n div.appendChild(addrIn)\n mod.path = [1, 2]\n addrIn.value = mod.path.toString()\n\n div.appendChild(document.createElement('br'))\n var btn = document.createElement('button')\n btn.style.padding = mods.ui.padding\n btn.style.margin = 1\n btn.appendChild(document.createTextNode('pump'))\n btn.addEventListener('click', function() {\n outputs.packet.event()\n })\n div.appendChild(btn)\n\n }\n //\n // local functions\n //\n function local_func() {\n console.log('internal func')\n }\n //\n // return values\n //\n return ({\n name: name,\n init: init,\n inputs: inputs,\n outputs: outputs,\n interface: interface\n })\n}())","top":"306","left":"5981","inputs":{},"outputs":{}},"0.38985999719909814":{"definition":"//\n// character input output\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n //\n // module globals\n //\n var mod = {}\n //\n // name\n //\n var name = 'stepsequencer'\n /*\n onedof takes a position triplet and feedrate\n and outputs three step commands, in sync\n */\n //\n // initialization\n //\n var init = function() {\n mod.sequencePosition = 0\n }\n //\n // inputs\n //\n var inputs = {\n sync: {\n type: 'event',\n event: function(evt) {\n // do beat sync\n trigger_func() // serves the justice\n }\n }\n }\n //\n // outputs\n //\n var outputs = {\n trigger: {\n type: 'event',\n event: function() {\n mods.output(mod, 'trigger', 1)\n }\n },\n steps: {\n type: 'steps',\n event: function(val) {\n mods.output(mod, 'steps', val)\n }\n },\n speed: {\n type: 'steps/s',\n event: function(val) {\n mods.output(mod, 'speed', val)\n }\n }\n }\n //\n // interface\n //\n var interface = function(div) {\n mod.div = div\n mod.in = [] // inputs\n\n div.appendChild(document.createTextNode('first position, speed:'))\n div.appendChild(document.createElement('br'))\n var in1 = document.createElement('input')\n in1.type = 'text'\n in1.size = mods.ui.cols\n in1.addEventListener('change', function() {\n mod.in[0] = in1.value.split(',')\n // trigger func that looks at mod.in[]s and checks length, builds array of arrays?\n console.log(mod.in[0])\n })\n div.appendChild(in1)\n mod.in[0] = ['0', '1000']\n in1.value = mod.in[0].toString()\n\n div.appendChild(document.createElement('br'))\n div.appendChild(document.createTextNode('second position, speed:'))\n div.appendChild(document.createElement('br'))\n var in2 = document.createElement('input')\n in2.type = 'text'\n in2.size = mods.ui.cols\n in2.addEventListener('change', function() {\n mod.in[1] = in2.value.split(',')\n // trigger func that looks at mod.in[]s and checks length, builds array of arrays?\n console.log(mod.in[1])\n })\n div.appendChild(in2)\n mod.in[1] = ['1000', '2000']\n in2.value = mod.in[1].toString()\n\n /*\n div.appendChild(document.createElement('br'))\n div.appendChild(document.createTextNode('third position, speed:'))\n div.appendChild(document.createElement('br'))\n var in3 = document.createElement('input')\n in3.type = 'text'\n in3.size = mods.ui.cols\n in3.addEventListener('change', function() {\n mod.in[2] = in1.value.split(',')\n // trigger func that looks at mod.in[]s and checks length, builds array of arrays?\n console.log(mod.in[2])\n })\n div.appendChild(in3)\n mod.in[2] = ['0', '400']\n in3.value = mod.in[2].toString()\n */\n\n div.appendChild(document.createElement('br'))\n var btn = document.createElement('button')\n btn.style.padding = mods.ui.padding\n btn.style.margin = 1\n btn.appendChild(document.createTextNode('clear'))\n btn.addEventListener('click', function() {\n in1.value = ''\n in2.value = ''\n //in3.value = ''\n })\n div.appendChild(btn)\n\n div.appendChild(document.createElement('br'))\n var btn = document.createElement('button')\n btn.style.padding = mods.ui.padding\n btn.style.margin = 1\n btn.appendChild(document.createTextNode('pump'))\n btn.addEventListener('click', function() {\n trigger_func()\n })\n div.appendChild(btn)\n }\n //\n // local functions\n //\n function trigger_func() {\n // decrement count, trigger next in seq. if not 0, loop around sequence agnostic to trigger logic\n outputs.steps.event(mod.in[mod.sequencePosition][0])\n outputs.speed.event(mod.in[mod.sequencePosition][1])\n outputs.trigger.event()\n mod.sequencePosition++;\n if (mod.sequencePosition >= mod.in.length) {\n mod.sequencePosition = 0\n }\n }\n //\n // return values\n //\n return ({\n name: name,\n init: init,\n inputs: inputs,\n outputs: outputs,\n interface: interface\n })\n}())","top":"302","left":"5224","inputs":{},"outputs":{}},"0.6300206027264619":{"definition":"//\n// delay event\n//\n// Neil Gershenfeld \n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n //\n // module globals\n //\n var mod = {}\n //\n // name\n //\n var name = 'delay event'\n //\n // initialization\n //\n var init = function() {\n mod.delay.value = 1000\n mod.countdown.value = 10\n mod.countdownBegin = 10\n mod.loop.checked = true\n }\n //\n // inputs\n //\n var inputs = {\n sync: {\n type: 'event',\n event: function(evt) {\n mod.input = evt.detail\n mod.event.value = evt.detail\n trigger_if()\n }\n },\n countdown: {\n type: 'count',\n event: function(evt) {\n mod.countdownBegin = evt.detail\n mod.countdown.value = evt.detail\n //console.log('count in: ', evt.detail)\n trigger_oncount()\n }\n }\n }\n //\n // outputs\n //\n var outputs = {\n trigger: {\n type: 'event',\n event: function() {\n mods.output(mod, 'trigger', mod.input)\n }\n }\n }\n //\n // interface\n //\n var interface = function(div) {\n mod.div = div\n //\n // delay label\n //\n var span = document.createElement('span')\n var text = document.createTextNode('waiting for event')\n mod.label = text\n span.appendChild(text)\n mod.labelspan = span\n div.appendChild(span)\n //\n // delay value\n //\n div.appendChild(document.createElement('br'))\n div.appendChild(document.createTextNode('delay (ms): '))\n var delayInput = document.createElement('input')\n delayInput.type = 'text'\n delayInput.size = 6\n delayInput.addEventListener('change', function() {})\n div.appendChild(delayInput)\n mod.delay = delayInput\n //\n // counts\n //\n div.appendChild(document.createElement('br'))\n div.appendChild(document.createTextNode('count down: '))\n var countInput = document.createElement('input')\n countInput.type = 'text'\n countInput.size = 6\n countInput.addEventListener('change', function() {\n mod.countdownBegin = countInput.value\n mod.countdown = countInput\n })\n div.appendChild(countInput)\n mod.countdownBegin = countInput.value\n mod.countdown = countInput\n // save countdown from for reset\n // implement reset\n //\n // loop or don't\n //\n div.appendChild(document.createElement('br'))\n div.appendChild(document.createTextNode('loop: '))\n var loopInput = document.createElement('input')\n loopInput.type = 'checkbox'\n //input.id = mod.div.id + 'loop'\n div.appendChild(loopInput)\n mod.loop = loopInput\n div.appendChild(document.createElement('br'))\n //\n // event value\n //\n div.appendChild(document.createElement('br'))\n div.appendChild(document.createTextNode('event value: '))\n var eventInput = document.createElement('input')\n eventInput.type = 'text'\n eventInput.size = 6\n eventInput.addEventListener('change', function() {})\n div.appendChild(eventInput)\n mod.event = eventInput\n }\n //\n // local functions\n //\n function delay_event() {\n mod.label.nodeValue = 'delaying event'\n mod.labelspan.style.fontWeight = 'bold'\n var delay = parseFloat(mod.delay.value)\n window.setTimeout(after_delay, delay)\n }\n\n function after_delay() {\n mod.label.nodeValue = 'waiting for event'\n mod.labelspan.style.fontWeight = 'normal'\n mod.countdown.value--\n outputs.trigger.event()\n }\n\n function trigger_if() {\n if (mod.countdown.value < 1) {\n if (mod.loop.checked) {\n mod.countdown.value = mod.countdownBegin\n delay_event()\n }\n } else {\n delay_event()\n }\n }\n\n function trigger_oncount() {\n console.log('trigger oncount, ', mod.countdown.value)\n if(mod.countdown.value < 1){\n // loop begin is also 0, nothing\n } else {\n //console.log('coundown begin')\n outputs.trigger.event()\n mod.countdown.value --\n }\n }\n //\n // return values\n //\n return ({\n name: name,\n init: init,\n inputs: inputs,\n outputs: outputs,\n interface: interface\n })\n}())","top":"297","left":"4756","inputs":{},"outputs":{}},"0.8730984119702436":{"definition":"//\n// event gate\n//\n// Neil Gershenfeld and Jake Read\n// (c) Massachusetts Institute of Technology 2016\n// \n// This work may be reproduced, modified, distributed, performed, and \n// displayed for any purpose, but must acknowledge the mods\n// project. Copyright is retained and must be preserved. The work is \n// provided as is; no warranty is provided, and users accept all \n// liability.\n//\n// closure\n//\n(function() {\n //\n // module globals\n //\n var mod = {}\n //\n // name\n //\n var name = 'event gate'\n //\n // initialization\n //\n var init = function() {\n mod.gate = 0\n mod.button_gate.childNodes[0].nodeValue = 'gate: closed'\n mod.event_in.value = '1'\n }\n //\n // inputs\n //\n var inputs = {\n trigger: {\n type: 'event',\n event: function(evt) {\n if (mod.gate === 0) {\n console.log('thou shall not pass')\n } else {\n outputs.output.event()\n }\n }\n },\n close: {\n type: 'event',\n event: function(evt){\n gate_close()\n }\n },\n open: {\n type: 'event',\n event: function(evt){\n gate_open()\n }\n }\n }\n //\n // outputs\n //\n var outputs = {\n output: {\n type: 'event',\n event: function() {\n mods.output(mod, 'output', mod.event_in.value)\n }\n }\n }\n //\n // interface\n //\n var interface = function(div) {\n mod.div = div\n \n // generate button\n mod.button_generate = make_button_input(div, 'generate')\n mod.button_generate.addEventListener('click', function(){\n outputs.output.event()\n })\n \n // gate mode\n mod.button_gate = make_button_input(div, '')\n mod.button_gate.addEventListener('click', function(){\n if(mod.gate === 1){\n gate_close()\n } else {\n gate_open()\n }\n })\n \n // event value\n mod.event_in = make_text_input(div, 'value: ', 6)\n mod.event_in.addEventListener('change', function(){\n //\n })\n }\n //\n // local functions\n //\n\n function gate_open(){\n mod.gate = 1\n mod.button_gate.childNodes[0].nodeValue = \"gate: open\"\n }\n\n function gate_close(){\n mod.gate = 0\n mod.button_gate.childNodes[0].nodeValue = \"gate: closed\"\n }\n\n //\n // return values\n //\n return ({\n name: name,\n init: init,\n inputs: inputs,\n outputs: outputs,\n interface: interface\n })\n}())","top":"691","left":"4764","inputs":{},"outputs":{}}},"links":["{\"source\":\"{\\\"id\\\":\\\"0.38985999719909814\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"trigger\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.37807284615053505\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"sync\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.38985999719909814\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"steps\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.37807284615053505\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"position\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.38985999719909814\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"speed\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.37807284615053505\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"speed\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6300206027264619\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"trigger\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.38985999719909814\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"sync\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.6300206027264619\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"trigger\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.8730984119702436\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"trigger\\\"}\"}","{\"source\":\"{\\\"id\\\":\\\"0.8730984119702436\\\",\\\"type\\\":\\\"outputs\\\",\\\"name\\\":\\\"output\\\"}\",\"dest\":\"{\\\"id\\\":\\\"0.6300206027264619\\\",\\\"type\\\":\\\"inputs\\\",\\\"name\\\":\\\"sync\\\"}\"}"]}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment