├── README ├── bytebeat.js ├── convert_collection.py ├── essay └── sawtooth.html ├── glitchparse.html ├── glitchparse.js ├── index.html ├── nodeglitchparse.js └── rpn.js /README: -------------------------------------------------------------------------------- 1 | You can play with this online at http://wry.me/bytebeat/ 2 | 3 | http://canonical.org/~kragen/bytebeat/ exlains what this is about. 4 | 5 | This was derived (with extensive overhauls now) from 6 | http://www.bemmu.com/music/index.html which, according to 7 | http://wurstcaptures.untergrund.net/music/, is by Bemmu and viznut. 8 | I've also adapted parts of the wurstcaptures page. 9 | 10 | Authors: 11 | Darius Bacon 12 | Kragen Sitaker 13 | -------------------------------------------------------------------------------- /bytebeat.js: -------------------------------------------------------------------------------- 1 | //"use strict"; 2 | 3 | function showAudioVisual(sound, player, viz) { 4 | player.src = makeAudioURI(sound); 5 | visualize(viz, sound, player); 6 | } 7 | 8 | 9 | function compileComposer(text) { 10 | // (0,eval) explained at 11 | // http://perfectionkills.com/global-eval-what-are-the-options/ 12 | return (0,eval)("(function(t) { return " 13 | + text.replace(/sin|cos|tan|floor|ceil/g, 14 | function(str) { return "Math."+str; }) 15 | + "})"); 16 | } 17 | 18 | 19 | // Sound generation into WAV format 20 | // See https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ 21 | 22 | function makeSound(composers, duration, rate, bytesPerSample) { 23 | if (bytesPerSample !== 1) 24 | throw "I only do 8-bit audio yet"; 25 | 26 | var nsamples = duration * rate; 27 | var nchannels = composers.length; 28 | var dataLength = nsamples * nchannels * bytesPerSample; 29 | 30 | var metadataLength = 12 + 24 + 8; 31 | var bytes = [].concat( 32 | // Header (length 12) 33 | cc("RIFF"), 34 | bytesFromU32(metadataLength + dataLength), 35 | cc("WAVE"), 36 | // "fmt " subchunk (length 24): 37 | cc("fmt "), 38 | bytesFromU32(16), // length of this subchunk's data 39 | bytesFromU16(1), // AudioFormat = 1 for PCM 40 | bytesFromU16(nchannels), 41 | bytesFromU32(rate), 42 | bytesFromU32(rate * nchannels * bytesPerSample), 43 | bytesFromU16(nchannels * bytesPerSample), 44 | bytesFromU16(8 * bytesPerSample), 45 | // "data" subchunk (length 8 + length(following samples)): 46 | cc("data"), 47 | bytesFromU32(dataLength) 48 | ); 49 | if (bytes.length !== metadataLength) 50 | throw "Assertion failure"; 51 | if (nchannels === 1) { 52 | var composer = composers[0]; 53 | for (var t = 0; t < nsamples; ++t) 54 | bytes.push(0xFF & composer(t)); 55 | } else if (nchannels === 2) { 56 | var composer0 = composers[0]; 57 | var composer1 = composers[1]; 58 | for (var t = 0; t < nsamples; ++t) { 59 | bytes.push(0xFF & composer0(t)); 60 | bytes.push(0xFF & composer1(t)); 61 | } 62 | } else 63 | throw "I only do 1- or 2-channel audio"; 64 | if (bytes.length !== metadataLength + dataLength) 65 | throw "Assertion failure"; 66 | 67 | return { 68 | duration: duration, 69 | rate: rate, 70 | nchannels: nchannels, 71 | nsamples: nsamples, 72 | bytesPerSample: bytesPerSample, 73 | bytes: bytes, 74 | channel0_8bit: function(t) { 75 | return bytes[metadataLength + t * nchannels]; 76 | }, 77 | channel1_8bit: (nchannels < 2 78 | ? function(t) { return 0; } 79 | : function(t) { 80 | return bytes[metadataLength + t * nchannels + 1]; 81 | }), 82 | }; 83 | } 84 | 85 | // String to array of byte values. 86 | function cc(str) { 87 | var result = []; 88 | for (var i = 0; i < str.length; ++i) 89 | result.push(str.charCodeAt(i)); // XXX check that it's a byte 90 | return result; 91 | } 92 | 93 | function bytesFromU16(v) { 94 | return [0xFF & v, 0xFF & (v>>8)]; 95 | } 96 | function bytesFromU32(v) { 97 | return [0xFF & v, 0xFF & (v>>8), 0xFF & (v>>16), 0xFF & (v>>24)]; 98 | } 99 | 100 | 101 | // URI encoding 102 | 103 | function makeAudioURI(sound) { 104 | return "data:audio/x-wav," + hexEncodeURI(sound.bytes); 105 | } 106 | 107 | var hexCodes = (function () { 108 | var result = []; 109 | for (var b = 0; b < 256; ++b) 110 | result.push((b < 16 ? "%0" : "%") + b.toString(16)); 111 | return result; 112 | })(); 113 | 114 | // [255, 0] -> "%ff%00" 115 | function hexEncodeURI(values) { 116 | var codes = []; 117 | for (var i = 0; i < values.length; ++i) 118 | codes.push(hexCodes[values[i]]); 119 | return codes.join(''); 120 | } 121 | 122 | 123 | // Visualization 124 | 125 | var prev_t; 126 | 127 | function visualize(canvas, sound, audio) { 128 | // A dot for each sample (green/blue for channel 0/1). 129 | canvasUpdate(canvas, function(pixbuf, width, height) { 130 | var p = 0; 131 | for (var y = 0; y < height; ++y) { 132 | for (var x = 0; x < width; ++x) { 133 | var t = height * x + y; 134 | pixbuf[p++] = 0; 135 | pixbuf[p++] = sound.channel0_8bit(t); 136 | pixbuf[p++] = sound.channel1_8bit(t); 137 | pixbuf[p++] = 0xFF; 138 | } 139 | } 140 | }); 141 | if (audio) { 142 | vizStop(); 143 | function vizKickoff() { 144 | vizStart(function() { updateViz(canvas, audio, sound); }, 145 | 33.33); 146 | } 147 | prev_t = null; 148 | audio.ontimeupdate = vizKickoff; 149 | vizKickoff(); // ontimeupdate does nothing on some browsers. 150 | } 151 | } 152 | 153 | var vizIntervalId; 154 | 155 | function vizStart(action, msecPerFrame) { 156 | if (!vizIntervalId) 157 | vizIntervalId = setInterval(action, msecPerFrame); 158 | } 159 | 160 | function vizStop() { 161 | if (vizIntervalId) clearInterval(vizIntervalId); 162 | vizIntervalId = 0; 163 | } 164 | 165 | function updateViz(canvas, audio, sound) { 166 | var t = Math.round(audio.currentTime * sound.rate); 167 | if (prev_t === t) 168 | return; // Player probably paused, don't waste CPU. 169 | var T = sound.duration * sound.rate; 170 | 171 | canvasUpdate(canvas, function(pixbuf, width, height) { 172 | if (prev_t !== null) { 173 | flip(prev_t); 174 | prev_t = null; 175 | } 176 | if (sound.nsamples <= t) 177 | vizStop(); 178 | else 179 | flip(prev_t = t); 180 | 181 | function flip(t) { 182 | t &= ~0xFF; // Reduce the 'oscilloscope jitter' 183 | wave(t); 184 | progress(t); 185 | } 186 | 187 | // A red waveform for the next 'width' samples. 188 | // Two waves for stereo, actually, but any all-zero wave is suppressed. 189 | function wave(t) { 190 | var prevSample0 = sound.channel0_8bit(t); 191 | var prevSample1 = sound.channel1_8bit(t); 192 | var after = Math.max(0, Math.min(T-t, width)); 193 | for (var x = 0; x < after; ++x) { 194 | var sample0 = (height / 256) * sound.channel0_8bit(t + x); 195 | var sample1 = (height / 256) * sound.channel1_8bit(t + x); 196 | var ranges = unionRanges(prevSample0, sample0, 197 | prevSample1, sample1); 198 | for (var r = 0; r < ranges.length; ++r) { 199 | var lo = ranges[r][0]; 200 | var hi = ranges[r][1]; 201 | if (lo | hi) { // Skip all-zero segments 202 | for (var y = height-1 - hi; y <= height-1 - lo; ++y) { 203 | var p = 4 * (width * y + x); 204 | pixbuf[p] ^= 0xFF; 205 | } 206 | } 207 | } 208 | prevSample0 = sample0; 209 | prevSample1 = sample1; 210 | } 211 | } 212 | 213 | // Return [[a_lo, a_hi], [b_lo, b_hi]] covering the same 214 | // ranges but nonintersecting. [a0, a1] are endpoints of a 215 | // range, not necessarily in order. 216 | function unionRanges(a0, a1, b0, b1) { 217 | var al = Math.min(a0, a1), ah = Math.max(a0, a1); 218 | var bl = Math.min(b0, b1), bh = Math.max(b0, b1); 219 | if (al < bl) { 220 | if (bl < ah) bl = ah + 1; 221 | } else { 222 | if (al < bh) al = bh + 1; 223 | } 224 | return [[al, ah], [bl, bh]]; 225 | } 226 | 227 | // A progress bar as a vertical line of translucency. 228 | function progress(t) { 229 | var x = Math.floor(t / height); 230 | if (x < width) { 231 | for (y = 0; y < height; ++y) { 232 | p = 4 * (width * y + x); 233 | pixbuf[p+3] ^= 0xC0; 234 | } 235 | } 236 | } 237 | }); 238 | } 239 | 240 | function canvasUpdate(canvas, f) { 241 | var ctx = canvas.getContext("2d"); 242 | var imageData = ctx.getImageData(0, 0, canvas.width, canvas.height); 243 | f(imageData.data, canvas.width, canvas.height); 244 | ctx.putImageData(imageData, 0, 0); 245 | } 246 | -------------------------------------------------------------------------------- /convert_collection.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | ## mu = 'L: ((t&4096)?((t*(t^t%255)|(t>>4))>>1):(t>>3)|((t&8192)?t<<2:t)) R: t*(((t>>9)^((t>>9)-1)^1)%13)' 4 | ## m = re.match(r'L: (.*) R: (.*)', mu) 5 | ## m.groups()[1] 6 | #. 't*(((t>>9)^((t>>9)-1)^1)%13)' 7 | 8 | text = open('music_formula_collection.txt').read() 9 | 10 | def main(): 11 | print """This is an automated mark-up of the 13 | collection of oneliner music formulas (version 2011-10-18) 14 | by viznut, Converted by convert_collection.py at 15 | https://github.com/darius/bytebeat. 16 | (The conversion is bound to have gotten at least a few things wrong.)'""" 17 | for head, body in chunk_input(): 18 | print '

%s

' % head.replace('=', '').strip() 19 | print '' 50 | 51 | import cgi, urllib 52 | 53 | def format_row(code0, code1, author, date, url, rate, rest): 54 | title = ' '.join(filter(None, [author, date, rest])) 55 | title = title or formula 56 | params = dict(code0=code0, title=title) 57 | if code1 is not None: params['code1'] = code1 58 | if rate is not None: params['samplerate'] = str(rate) 59 | if url is not None: params['source'] = url 60 | return ('%s' 61 | % (my_urlencode(params), cgi.escape(title))) 62 | 63 | def my_urlencode(params): 64 | def quote(x): 65 | return urllib.quote(x, safe='') 66 | return '&'.join('%s=%s' % (quote(k), quote(v)) 67 | for k, v in params.items()) 68 | 69 | def chunk_input(): 70 | return chunkify(skip_head(text.splitlines())) 71 | 72 | def skip_head(lines): 73 | lines = iter(lines) 74 | for line in lines: 75 | if line.startswith('==='): 76 | break 77 | yield line 78 | for line in lines: 79 | yield line 80 | 81 | def chunkify(lines): 82 | head = next(lines) 83 | while True: 84 | body = [] 85 | next_head = None 86 | 87 | while next_head is None: 88 | blank = next(lines, None) 89 | if blank is None: 90 | break 91 | assert blank == '' 92 | comment = next(lines) 93 | if comment.startswith('==='): 94 | next_head = comment 95 | break 96 | assert comment.startswith('//') 97 | formula = next(lines) 98 | body.append((comment, formula)) 99 | 100 | yield head, body 101 | if next_head is None: 102 | break 103 | head = next_head 104 | 105 | if __name__ == '__main__': 106 | main() 107 | 108 | ### main() 109 | -------------------------------------------------------------------------------- /essay/sawtooth.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Sawtooth 4 | 5 | 6 | [Fork darius/bytebeat 7 | to help write this tutorial.] 8 | 9 |

The simplest audible program is 10 |

11 | 12 | 13 | 14 |
15 | 16 |

17 | 18 |

19 |

Try doubling the frequency. 20 |

21 | 22 | 69 | 70 | -------------------------------------------------------------------------------- /glitchparse.html: -------------------------------------------------------------------------------- 1 | /* glitch:// URL: 2 | 3 | 4 |
5 | 24 | -------------------------------------------------------------------------------- /glitchparse.js: -------------------------------------------------------------------------------- 1 | /* 2 | * Convert glitch:// URLs from glitched and GlitchMachine into infix JS. 3 | * 4 | * Remaining problems: 5 | * I haven’t yet implemented PUT DROP LSHIFT PICK < > == (bcjoqstu). 6 | * 7 | * Of those, PUT and PICK are very tricky. < > == are moderately 8 | * tricky. LSHIFT is easy; I don’t know why I haven’t seen it in use. 9 | * 10 | * Division isn’t quite right. glitch:// division by zero generates 11 | * 0, either via / or %. JS generates NaN. The difference is only 12 | * relevant if the value is then transformed in a way that would cause 13 | * a 0 division result to generate a non-zero sample, and not always 14 | * even then. 15 | * 16 | * Worse, division generates fractions in JS, while glitch:// is 17 | * all-unsigned-32-bit-values. The difference in this case is only 18 | * relevant if there’s a path for those fractional bits to make their 19 | * way into the output. Any bitwise operation will discard them, and 20 | * most arithmetic operations merely preserve them: addition and 21 | * subtraction of integers, division by numbers greater than 1, and 22 | * multiplication by numbers between -1 and 1. The only way for those 23 | * fractional bits to escape and cause havoc is addition or subtraction 24 | * of another number with fractional bits, division by a number less than 25 | * 1, or multiplication by a number greater than 1. In those cases, the 26 | * division result can be explicitly truncated using ~~, if it matters. 27 | * Annotating parse tree nodes to keep track of which ones have possible 28 | * fractional parts would be sufficient to avoid the majority of these 29 | * cases, since division results are most commonly fed immediately to 30 | * a bitwise operator. glitch://martians!a64d!a80e64h1fe!a6km is a 31 | * case where this matters a great deal, although I’m not sure why. 32 | * 33 | * Glitchparse doesn’t always take advantage of associativity of 34 | * associative operators to avoid unnecessary parens. For example, 35 | * glitch://cube!aadad is correctly translated to t * t * t, but the 36 | * exactly equivalent glitch://cube!aaadd is translated to t * (t * t), 37 | * with superfluous parentheses. 38 | */ 39 | 40 | glitchparse = {} 41 | if (typeof exports !== 'undefined') glitchparse = exports 42 | 43 | glitchparse.infix_of = function(glitch_url) { 44 | var stack = [] 45 | , contents = /^glitch:\/\/[^!]*!(.*)/.exec(glitch_url) 46 | , push = function(x) { stack.push(x) } 47 | , pop = function() { return stack.pop() } 48 | , binop = function(op) { 49 | return function() { var b = pop(); push([pop(), op, b]) } 50 | } 51 | , nextVar = 'a' 52 | , seqExpressions = [] 53 | , defineVar = function(expr) { 54 | var varName = nextVar 55 | nextVar = String.fromCharCode(nextVar.charCodeAt(0) + 1) 56 | // XXX handle more than a few vars by changing name! 57 | seqExpressions.push(varName + ' = ' + glitchparse.ast_to_js(expr)) 58 | return varName 59 | } 60 | , ops = { a: function() { push('t') } 61 | , d: binop('*') 62 | , e: binop('/') // XXX see comment at top of file 63 | , f: binop('+') 64 | , g: binop('-') 65 | , h: binop('%') 66 | , k: binop('>>>') 67 | , l: binop('&') 68 | , m: binop('|') 69 | , n: binop('^') 70 | , o: function() { push(['~', pop()]) } 71 | , p: function() { var v = defineVar(pop()); push(v); push(v) } 72 | , r: function() { var a = pop(); var b = pop(); push(a); push(b) } 73 | } 74 | 75 | if (!contents) throw Error("Can't parse " + glitch_url) 76 | 77 | // Iterate over the tokens using the string replace method. 78 | // XXX would be nice to notice unhandled data! 79 | contents[1].replace(/[0-9A-F]+|[a-u!.]/g, function(op) { 80 | if (/[a-u]/.test(op)) return ops[op]() 81 | if (op === '!' || op === '.' ) return 82 | return push(parseInt(op, 16)) 83 | }) 84 | 85 | seqExpressions.push(glitchparse.ast_to_js(pop())) 86 | return seqExpressions.join(', ') 87 | } 88 | 89 | glitchparse.ast_to_js = function(ast) { 90 | //console.log(require('sys').inspect(ast, 0, 20)) 91 | var reallyBigNumber = 100 92 | return glitchparse.ast_to_js_(ast, reallyBigNumber, undefined) 93 | } 94 | 95 | glitchparse.ast_to_js_ = function(ast, parentPrecedence, leftOrRight) { 96 | if (typeof ast === 'string' || typeof ast === 'number') return ast 97 | 98 | if (typeof ast === 'undefined') throw Error("Stack underflow!") 99 | 100 | if (ast.length === 2) { 101 | // The unary operators would be at item 3 in the precedence list. 102 | return ast[0] + glitchparse.ast_to_js_(ast[1], 3, 'right') 103 | } 104 | 105 | // Binop case. 106 | var op = ast[1] 107 | , precedence = glitchparse.binaryPrecedence(ast[1]) 108 | , body = [ glitchparse.ast_to_js_(ast[0], precedence, 'left') 109 | , op 110 | , glitchparse.ast_to_js_(ast[2], precedence, 'right') 111 | ] 112 | .join(' ') 113 | 114 | if (precedence < parentPrecedence) return body 115 | 116 | // All operators we currently handle associate left-to-right. 117 | if (precedence === parentPrecedence && leftOrRight === 'left') return body 118 | 119 | // Parenthesize because parent operator has tighter precedence. 120 | return '(' + body + ')' 121 | } 122 | 123 | glitchparse.binaryPrecedence = function(op) { 124 | // 125 | var precedence = [ [ '.', '[]', 'new' ] 126 | , [ '()' ] 127 | , [ '++', '--' ] 128 | , [ ] // unary operators omitted because of conflict 129 | , [ '*', '/', '%' ] 130 | , [ '+', '-' ] 131 | , [ '<<', '>>', '>>>' ] 132 | , [ '<', '<=', '>', '>=', 'in', 'instanceof' ] 133 | , [ '==', '!=', '===', '!==' ] 134 | , [ '&' ] 135 | , [ '^' ] 136 | , [ '|' ] 137 | , [ '&&' ] 138 | , [ '||' ] 139 | , [ ] // '?:' 140 | , [ 141 | // Assignment operators omitted because: 142 | // 1. They don’t exist in glitch:// URLs 143 | // 2. They associate right-to-left, unlike all 144 | // the operators we actually handle. 145 | ] 146 | , [ ',' ] 147 | ] 148 | 149 | for (var ii = 0; ii < precedence.length; ii++) { 150 | if (precedence[ii].indexOf(op) !== -1) return ii 151 | } 152 | } 153 | 154 | 155 | glitchparse.test = function() { 156 | var starlost = 'glitch://starlost!aFFha1FFhn3d' 157 | , starlost_infix = '(t % 255 ^ t % 511) * 3' 158 | , assert = require('assert') 159 | , ast_to_js = glitchparse.ast_to_js 160 | , infix_of = glitchparse.infix_of 161 | 162 | assert.equal(ast_to_js('t'), 't') 163 | assert.equal(ast_to_js(['t', '^', 34]), 't ^ 34') 164 | assert.equal(ast_to_js([['t', '*', 4], '%', 128]), 't * 4 % 128') 165 | assert.equal(ast_to_js(['t', '*', [4, '%', 128]]), 't * (4 % 128)') 166 | assert.equal(ast_to_js(['~', ['t', '*', 4]]), '~(t * 4)') 167 | assert.equal(infix_of(starlost), starlost_infix) 168 | assert.equal(infix_of( 169 | 'glitch://pickled_glitches!aa7D00e6h25Cfhao25Chlp!a40hl!a25De80h80fd' 170 | ), 171 | 'a = t % (t / 32000 % 6 + 604) & ~t % 604, ' + 172 | '(a & t % 64) * (t / 605 % 128 + 128)' 173 | ) 174 | } 175 | 176 | glitchparse.test() 177 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | Play 5 | and optionally 6 |
for 7 | seconds at 8 | 8 kHz 9 | 11 kHz 10 | 22 kHz 11 | 32 kHz 12 | 44 kHz. 13 | The expressions are in 14 | JavaScript 15 | RPN. 16 |
Call this tune 17 | . 18 |
19 | 21 |
22 | 23 |

24 | 25 |

26 |

Once you've made a new tune, you can save or link to it: . 27 | 28 | 29 | 30 |


31 | Try some examples. 32 | 33 | In RPN mode, Glitch Machine 35 | expressions should work. For example, t 37 | 10 >> 42 * t * . 38 | 39 |

See Kragen Sitaker's 40 | overview of bytebeat for an idea of what to do with this web toy 41 | by Darius Bacon. 42 | Source code on GitHub. 43 | Based on Bemmu and 44 | viznut's 45 | and rarefluid's. 46 | 47 | 48 | 49 | 223 | 224 | -------------------------------------------------------------------------------- /nodeglitchparse.js: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/node 2 | var glitchparse = require('./glitchparse') 3 | , sys = require('sys') 4 | , octo = 'glitch://octo!a2k14had!a2000he!a8!a11k3h1fde!m!aEk7Fhn!20g' 5 | 6 | 7 | sys.puts(glitchparse.infix_of(process.argv[2])) 8 | -------------------------------------------------------------------------------- /rpn.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | function jsFromRPN(string) { 4 | try { 5 | return jsFromRPNSource(string); 6 | } catch (err) { 7 | return jsFromURI(string); 8 | } 9 | } 10 | 11 | function jsFromRPNSource(string) { 12 | return translate(string.match(/\S+/g)); 13 | 14 | function translate(tokens) { 15 | var stack = makeCyclicStack(); 16 | for (var i = 0; i < tokens.length; ++i) 17 | simulate(tokens[i], opTable, stack); 18 | return stack.compile(); 19 | } 20 | } 21 | 22 | function jsFromURI(string) { 23 | var stack = makeCyclicStack(); 24 | var chars = string.substr(string.search(/!/) + 1).split(''); 25 | for (var i = 0; i < chars.length; ) { 26 | var literal = ''; 27 | while (i < chars.length && chars[i].match(/[0123456789ABCDEF]/)) 28 | literal += chars[i++]; 29 | if (literal !== '') 30 | stack.push(parseInt(literal, 16)); 31 | else if (chars[i] === '.' || chars[i] === '!') 32 | ++i; 33 | else 34 | simulate(chars[i++], shortOpTable, stack); 35 | } 36 | return stack.compile(); 37 | } 38 | 39 | function makeCyclicStack() { 40 | var stmts = []; 41 | var mask = 0xFF; 42 | var sp = -1; 43 | 44 | function S() { 45 | return 'this.S[' + (sp & mask) + ']'; 46 | } 47 | function pop() { 48 | var result = S(); 49 | --sp; 50 | return result; 51 | } 52 | function push(expr) { 53 | ++sp; 54 | stmts.push(S() + ' = ' + expr + ','); 55 | } 56 | return { 57 | compile: function() { 58 | if (sp < 0) 59 | return '0'; 60 | return ('((this.S ? 0 : this.S = new Array(256)), ' 61 | + stmts.concat(pop() + ')').join(' ')); 62 | }, 63 | pick: function() { 64 | push('this.S[(' + (sp-1) + '-' + pop() + ')&' + mask + ']'); 65 | }, 66 | push: push, 67 | pop: pop, 68 | }; 69 | } 70 | 71 | function simulate(op, table, stack) { 72 | if (table[op]) 73 | table[op](stack); 74 | else if (!isNaN(op)) 75 | stack.push(0xFFFFFFFF & op); // It's a number 76 | else 77 | throw "Unknown op: " + op; 78 | } 79 | 80 | function literalOp(op) { 81 | return function(stack) { 82 | stack.push(op); 83 | }; 84 | } 85 | 86 | // XXX use e|0 or e>>>0, depending on if we want signed or unsigned 87 | 88 | function masked(e) { 89 | return '(' + e + ')>>>0'; 90 | } 91 | function unmasked(e) { 92 | return '(' + e + ')'; 93 | } 94 | 95 | function prefixOp(op, masker) { 96 | return function(stack) { 97 | var z = stack.pop(); 98 | stack.push(masker(op + z)); 99 | }; 100 | } 101 | 102 | function infixOp(op, masker) { 103 | return function(stack) { 104 | var z = stack.pop(); 105 | var y = stack.pop(); 106 | stack.push(masker(y + op + z)); 107 | }; 108 | } 109 | 110 | function infixRel(op) { 111 | return function(stack) { 112 | var z = stack.pop(); 113 | var y = stack.pop(); 114 | stack.push('(' + y + op + z + ' ? 0xFFFFFFFF : 0)'); 115 | }; 116 | } 117 | 118 | // Based on http://paste.ubuntu.com/733764/ 119 | // and http://paste.ubuntu.com/733829/ 120 | var opTable = { 121 | 't': literalOp('t'), 122 | 'drop': function(stack) { 123 | stack.pop(); 124 | }, 125 | 126 | '*': infixOp('*', masked), 127 | '/': function(stack) { 128 | var z = stack.pop(); 129 | var y = stack.pop(); 130 | stack.push('(' + z + '?(' + y+'/'+z + ')>>>0:0)'); 131 | }, 132 | '+': infixOp('+', masked), 133 | '-': infixOp('-', masked), 134 | '%': function(stack) { 135 | var z = stack.pop(); 136 | var y = stack.pop(); 137 | stack.push('(' + z + '?(' + y+'%'+z + ')>>>0:0)'); 138 | }, 139 | 140 | '<<': infixOp('<<', unmasked), // XXX JS only looks at low 5 bits of count 141 | '>>': infixOp('>>>', unmasked), // XXX JS only looks at low 5 bits of count 142 | '&': infixOp('&', unmasked), 143 | '|': infixOp('|', unmasked), 144 | '^': infixOp('^', unmasked), 145 | '~': prefixOp('~', unmasked), 146 | 147 | 'dup': function(stack) { 148 | var z = stack.pop(); 149 | stack.push(z); 150 | stack.push(z); 151 | }, 152 | 'pick': function(stack) { 153 | stack.pick(); 154 | }, 155 | 'swap': function(stack) { 156 | var z = stack.pop(); 157 | var y = stack.pop(); 158 | stack.push(z); 159 | stack.push(y); 160 | }, 161 | 162 | '>': infixRel('>'), 163 | '<': infixRel('<'), 164 | '=': infixRel('==='), 165 | }; 166 | 167 | https://github.com/erlehmann/libglitch/blob/master/FORMAT-draft-erlehmann 168 | var shortOpTable = { 169 | 'a': opTable['t'], 170 | 'c': opTable['drop'], 171 | 172 | 'd': opTable['*'], 173 | 'e': opTable['/'], 174 | 'f': opTable['+'], 175 | 'g': opTable['-'], 176 | 'h': opTable['%'], 177 | 178 | 'j': opTable['<<'], 179 | 'k': opTable['>>'], 180 | 'l': opTable['&'], 181 | 'm': opTable['|'], 182 | 'n': opTable['^'], 183 | 'o': opTable['~'], 184 | 185 | 'p': opTable['dup'], 186 | 'q': opTable['pick'], 187 | 'r': opTable['swap'], 188 | 189 | 's': opTable['<'], 190 | 't': opTable['>'], 191 | 'u': opTable['='], 192 | }; 193 | --------------------------------------------------------------------------------