Example using Xinze's code of how to decouple grid based animations using random noise applied to phase.
-
-
Save dribnet/f6329949994c56d8c5476600958297af to your computer and use it in GitHub Desktop.
2020 MDDN342 Assignment 1: Living Wallpaper
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
license: mit | |
height: 540 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function encode64(input) { | |
var output = "", i = 0, l = input.length, | |
key = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=", | |
chr1, chr2, chr3, enc1, enc2, enc3, enc4; | |
while (i < l) { | |
chr1 = input.charCodeAt(i++); | |
chr2 = input.charCodeAt(i++); | |
chr3 = input.charCodeAt(i++); | |
enc1 = chr1 >> 2; | |
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4); | |
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6); | |
enc4 = chr3 & 63; | |
if (isNaN(chr2)) enc3 = enc4 = 64; | |
else if (isNaN(chr3)) enc4 = 64; | |
output = output + key.charAt(enc1) + key.charAt(enc2) + key.charAt(enc3) + key.charAt(enc4); | |
} | |
return output; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var canvasWidth = 1920; | |
var canvasHeight = 1080; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
var canvasWidth = 960; | |
var canvasHeight = 540; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script> | |
<script src="https://d3js.org/d3-random.v1.min.js"></script> | |
<script language="javascript" type="text/javascript" src="z_purview_helper.js"></script> | |
<script language="javascript" type="text/javascript" src="z_focused_random.js"></script> | |
<script language="javascript" type="text/javascript" src="p5.func.js"></script> | |
<script type="text/javascript" src="LZWEncoder.js"></script> | |
<script type="text/javascript" src="NeuQuant.js"></script> | |
<script type="text/javascript" src="GIFEncoder.js"></script> | |
<script type="text/javascript" src="b64.js"></script> | |
<script type="text/javascript" src="z_recorder.js"></script> | |
<script language="javascript" type="text/javascript" src="simplex-noise.js"></script> | |
<script language="javascript" type="text/javascript" src="canvas_size_normal.js"></script> | |
<script language="javascript" type="text/javascript" src="code.js"></script> | |
<style> body {padding: 0; margin: 0;} </style> | |
</head> | |
<body style="background-color:white"> | |
<div class="outer"> | |
<div class="inner"> | |
<div id="canvasContainer"></div> | |
</div> | |
</div> | |
</table> | |
<br> | |
<a href="sketch.html">sketch</a><br> | |
<a href="code.js">source code</a><br> | |
<a href="preview.jpg">preview image</a><br> | |
<a href="code_l.html">(large)</a><br> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const num_across = 4; | |
const num_down = 3; | |
const cell_width = canvasWidth / num_across; | |
const cell_height = canvasHeight / num_down; | |
let x_grid_locations = []; | |
let y_grid_locations = []; | |
let anchors = []; | |
const frameMax = 100; | |
let recording = false; | |
let gifRecorder = null; | |
function storeGridPoints(param1, param2) { | |
x_grid_locations = new Array(num_across+1); | |
y_grid_locations = new Array(num_down+1); | |
for (let i = 0; i < num_across+1; i++) { | |
let x = map(i, 0, num_across, 0, width); | |
x_grid_locations[i] = x; | |
} | |
for (let i = 0; i < num_down+1; i++) { | |
let y = map(i, 0, num_down, 0, height); | |
y_grid_locations[i] = y; | |
} | |
} | |
function setup () { | |
let main_canvas = createCanvas(canvasWidth, canvasHeight); | |
main_canvas.parent('canvasContainer'); | |
background("#99d9ff"); | |
anchors.push([1*cell_width/5, 1*cell_height/4]); | |
anchors.push([4*cell_width/5, 1*cell_height/4]); | |
anchors.push([4*cell_width/5, 3*cell_height/4]); | |
anchors.push([1*cell_width/5, 3*cell_height/4]); | |
storeGridPoints(); | |
// frameRate(10); | |
} | |
function draw () { | |
let moon_size = canvasHeight/20; | |
let rect_size = canvasHeight/14; | |
let rect_size2 = rect_size/1.414; | |
let star_size = canvasHeight/10; | |
let cur_frame = frameCount % frameMax; | |
let cur_frac = map(cur_frame, 0, frameMax, 0, 1); | |
strokeWeight(4); | |
stroke(222, 237, 251); | |
background(7, 23, 37); | |
// draw the background circles | |
for (let i = 0; i < num_down; i=i+1) { | |
for (let j = 0; j < num_across; j=j+1) { | |
let y1 = y_grid_locations[i]; | |
let x1 = x_grid_locations[j]; | |
fill(222, 237, 251); | |
ellipse(x1 + cell_width/2, y1 + cell_height/2, moon_size); | |
ellipse(x1 + cell_width/2, y1 + cell_height/2-star_size, moon_size/4); | |
ellipse(x1 + cell_width/2 +star_size/1.5, y1 + cell_height/2-star_size/1.5, moon_size/4); | |
ellipse(x1 + cell_width/2 +star_size, y1 + cell_height/2, moon_size/4); | |
ellipse(x1 + cell_width/2 +star_size/1.5, y1 + cell_height/2+star_size/1.5, moon_size/4); | |
ellipse(x1 + cell_width/2, y1 + cell_height/2+star_size, moon_size/4); | |
ellipse(x1 + cell_width/2-star_size/1.5, y1 + cell_height/2+star_size/1.5, moon_size/4); | |
ellipse(x1 + cell_width/2-star_size, y1 + cell_height/2, moon_size/4); | |
ellipse(x1 + cell_width/2-star_size/1.5, y1 + cell_height/2-star_size/1.5, moon_size/4); | |
fill(7, 23, 37); | |
ellipse(x1 + cell_width/2.12, y1 + cell_height/2, moon_size/2); | |
noFill(); | |
beginShape(); | |
vertex(x1+ cell_width/2, y1+ cell_height/2 -rect_size); | |
vertex(x1+ cell_width/2+rect_size, y1+ cell_height/2); | |
vertex(x1+ cell_width/2, y1+ cell_height/2+rect_size); | |
vertex(x1+ cell_width/2-rect_size, y1+ cell_height/2); | |
endShape(CLOSE); | |
beginShape(); | |
vertex(x1+ cell_width/2-rect_size2, y1+ cell_height/2-rect_size2); | |
vertex(x1+ cell_width/2+rect_size2, y1+ cell_height/2-rect_size2); | |
vertex(x1+ cell_width/2+rect_size2, y1+ cell_height/2+rect_size2); | |
vertex(x1+ cell_width/2-rect_size2, y1+ cell_height/2+rect_size2); | |
endShape(CLOSE); | |
//rect(x1 + cell_width/2.38, y1 + cell_height/2.34, rect_size, rect_size); | |
// rect(x1 + cell_width/2.38, y1 + cell_height/2.34, rect_size, rect_size); | |
let curNoise = getNoiseValue(i, j, 0, "phase", 0, 2, 0.5); | |
let curSlip = getNoiseValue(i, j, cur_frac, "slip", 0.0, 0.1, 2); | |
let active_frac = (cur_frac + curNoise + curSlip) % 1.0; | |
// print(cur_frac1, cur_frac2); | |
for(let i=0; i<4; i++) { | |
let cur_anchor = anchors[i]; | |
let next_i = (i+1) % 4; | |
let next_anchor = anchors[next_i]; | |
let cur_x = map(active_frac, 0, 1, cur_anchor[0], next_anchor[0]); | |
let cur_y = map(active_frac, 0, 1, cur_anchor[1], next_anchor[1]); | |
fill(222, 237, 251); | |
ellipse(x1+cur_x, y1+cur_y, 10); | |
noFill(); | |
} | |
} | |
} | |
} | |
function keyTyped() { | |
if (key == '!') { | |
saveBlocksImages(); | |
} | |
else if (key == '@') { | |
saveBlocksImages(true); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script> | |
<script src="https://d3js.org/d3-random.v1.min.js"></script> | |
<script language="javascript" type="text/javascript" src="z_purview_helper.js"></script> | |
<script language="javascript" type="text/javascript" src="z_focused_random.js"></script> | |
<script language="javascript" type="text/javascript" src="p5.func.js"></script> | |
<script type="text/javascript" src="LZWEncoder.js"></script> | |
<script type="text/javascript" src="NeuQuant.js"></script> | |
<script type="text/javascript" src="GIFEncoder.js"></script> | |
<script type="text/javascript" src="b64.js"></script> | |
<script type="text/javascript" src="z_recorder.js"></script> | |
<script language="javascript" type="text/javascript" src="simplex-noise.js"></script> | |
<script language="javascript" type="text/javascript" src="canvas_size_large.js"></script> | |
<script language="javascript" type="text/javascript" src="code.js"></script> | |
<style> body {padding: 0; margin: 0;} </style> | |
</head> | |
<body style="background-color:white"> | |
<div class="outer"> | |
<div class="inner"> | |
<div id="canvasContainer"></div> | |
</div> | |
</div> | |
</table> | |
<br> | |
<a href="sketch.html">sketch</a><br> | |
<a href="code.html">(normal)</a><br> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/p5.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/0.8.0/addons/p5.dom.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script> | |
<script src="https://d3js.org/d3-random.v1.min.js"></script> | |
<script language="javascript" type="text/javascript" src="z_purview_helper.js"></script> | |
<script language="javascript" type="text/javascript" src="z_focused_random.js"></script> | |
<script language="javascript" type="text/javascript" src="p5.func.js"></script> | |
<script type="text/javascript" src="LZWEncoder.js"></script> | |
<script type="text/javascript" src="NeuQuant.js"></script> | |
<script type="text/javascript" src="GIFEncoder.js"></script> | |
<script type="text/javascript" src="b64.js"></script> | |
<script type="text/javascript" src="z_recorder.js"></script> | |
<script language="javascript" type="text/javascript" src="canvas_size_xlarge.js"></script> | |
<script language="javascript" type="text/javascript" src="code.js"></script> | |
<style> body {padding: 0; margin: 0;} </style> | |
</head> | |
<body style="background-color:white"> | |
<div class="outer"> | |
<div class="inner"> | |
<div id="canvasContainer"></div> | |
</div> | |
</div> | |
</table> | |
<br> | |
<a href="sketch.html">sketch</a><br> | |
<a href="code.html">(normal)</a><br> | |
<a href="code_l.html">(large)</a><br> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This class lets you encode animated GIF files | |
* Base class : http://www.java2s.com/Code/Java/2D-Graphics-GUI/AnimatedGifEncoder.htm | |
* @author Kevin Weiner (original Java version - [email protected]) | |
* @author Thibault Imbert (AS3 version - bytearray.org) | |
* @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) | |
* @version 0.1 AS3 implementation | |
*/ | |
GIFEncoder = function() { | |
for (var i = 0, chr = {}; i < 256; i++) | |
chr[i] = String.fromCharCode(i); | |
function ByteArray() { | |
this.bin = []; | |
} | |
ByteArray.prototype.getData = function() { | |
for (var v = '', l = this.bin.length, i = 0; i < l; i++) | |
v += chr[this.bin[i]]; | |
return v; | |
}; | |
ByteArray.prototype.writeByte = function(val) { | |
this.bin.push(val); | |
}; | |
ByteArray.prototype.writeUTFBytes = function(string) { | |
for (var l = string.length, i = 0; i < l; i++) | |
this.writeByte(string.charCodeAt(i)); | |
}; | |
ByteArray.prototype.writeBytes = function(array, offset, length) { | |
for (var l = length || array.length, i = offset || 0; i < l; i++) | |
this.writeByte(array[i]); | |
}; | |
var exports = {}; | |
var width; // image size | |
var height; | |
var transparent = null; // transparent color if given | |
var transIndex; // transparent index in color table | |
var repeat = -1; // no repeat | |
var delay = 0; // frame delay (hundredths) | |
var started = false; // ready to output frames | |
var out; | |
var image; // current frame | |
var pixels; // BGR byte array from frame | |
var indexedPixels; // converted frame indexed to palette | |
var colorDepth; // number of bit planes | |
var colorTab; // RGB palette | |
var usedEntry = []; // active palette entries | |
var palSize = 7; // color table size (bits-1) | |
var dispose = -1; // disposal code (-1 = use default) | |
var closeStream = false; // close stream when finished | |
var firstFrame = true; | |
var sizeSet = false; // if false, get size from first frame | |
var sample = 10; // default sample interval for quantizer | |
var comment = "Generated by jsgif (https://github.com/antimatter15/jsgif/)"; // default comment for generated gif | |
/** | |
* Sets the delay time between each frame, or changes it for subsequent frames | |
* (applies to last frame added) | |
* int delay time in milliseconds | |
* @param ms | |
*/ | |
var setDelay = exports.setDelay = function setDelay(ms) { | |
delay = Math.round(ms / 10); | |
}; | |
/** | |
* Sets the GIF frame disposal code for the last added frame and any | |
* | |
* subsequent frames. Default is 0 if no transparent color has been set, | |
* otherwise 2. | |
* @param code | |
* int disposal code. | |
*/ | |
var setDispose = exports.setDispose = function setDispose(code) { | |
if (code >= 0) dispose = code; | |
}; | |
/** | |
* Sets the number of times the set of GIF frames should be played. Default is | |
* 1; 0 means play indefinitely. Must be invoked before the first image is | |
* added. | |
* | |
* @param iter | |
* int number of iterations. | |
* @return | |
*/ | |
var setRepeat = exports.setRepeat = function setRepeat(iter) { | |
if (iter >= 0) repeat = iter; | |
}; | |
/** | |
* Sets the transparent color for the last added frame and any subsequent | |
* frames. Since all colors are subject to modification in the quantization | |
* process, the color in the final palette for each frame closest to the given | |
* color becomes the transparent color for that frame. May be set to null to | |
* indicate no transparent color. | |
* @param | |
* Color to be treated as transparent on display. | |
*/ | |
var setTransparent = exports.setTransparent = function setTransparent(c) { | |
transparent = c; | |
}; | |
/** | |
* Sets the comment for the block comment | |
* @param | |
* string to be insterted as comment | |
*/ | |
var setComment = exports.setComment = function setComment(c) { | |
comment = c; | |
}; | |
/** | |
* The addFrame method takes an incoming BitmapData object to create each frames | |
* @param | |
* BitmapData object to be treated as a GIF's frame | |
*/ | |
var addFrame = exports.addFrame = function addFrame(im, is_imageData) { | |
if ((im === null) || !started || out === null) { | |
throw new Error("Please call start method before calling addFrame"); | |
} | |
var ok = true; | |
try { | |
if (!is_imageData) { | |
image = im.getImageData(0, 0, im.canvas.width, im.canvas.height).data; | |
if (!sizeSet) setSize(im.canvas.width, im.canvas.height); | |
} else { | |
if(im instanceof ImageData) { | |
image = im.data; | |
if(!sizeset || width!=im.width || height!=im.height) { | |
setSize(im.width,im.height); | |
} else { | |
} | |
} else if(im instanceof Uint8ClampedArray) { | |
if(im.length==(width*height*4)) { | |
image=im; | |
} else { | |
console.log("Please set the correct size: ImageData length mismatch"); | |
ok=false; | |
} | |
} else { | |
console.log("Please provide correct input"); | |
ok=false; | |
} | |
} | |
getImagePixels(); // convert to correct format if necessary | |
analyzePixels(); // build color table & map pixels | |
if (firstFrame) { | |
writeLSD(); // logical screen descriptior | |
writePalette(); // global color table | |
if (repeat >= 0) { | |
// use NS app extension to indicate reps | |
writeNetscapeExt(); | |
} | |
} | |
writeGraphicCtrlExt(); // write graphic control extension | |
if (comment !== '') { | |
writeCommentExt(); // write comment extension | |
} | |
writeImageDesc(); // image descriptor | |
if (!firstFrame) writePalette(); // local color table | |
writePixels(); // encode and write pixel data | |
firstFrame = false; | |
} catch (e) { | |
ok = false; | |
} | |
return ok; | |
}; | |
/** | |
* @description: Downloads the encoded gif with the given name | |
* No need of any conversion from the stream data (out) to base64 | |
* Solves the issue of large file sizes when there are more frames | |
* and does not involve in creation of any temporary data in the process | |
* so no wastage of memory, and speeds up the process of downloading | |
* to just calling this function. | |
* @parameter {String} filename filename used for downloading the gif | |
*/ | |
var download = exports.download = function download(filename) { | |
if(out===null || closeStream==false) { | |
console.log("Please call start method and add frames and call finish method before calling download"); | |
} else { | |
filename= filename !== undefined ? ( filename.endsWith(".gif")? filename: filename+".gif" ): "download.gif"; | |
var templink = document.createElement("a"); | |
templink.download=filename; | |
templink.href= URL.createObjectURL(new Blob([new Uint8Array(out.bin)], {type : "image/gif" } )); | |
templink.click(); | |
} | |
} | |
/** | |
* Adds final trailer to the GIF stream, if you don't call the finish method | |
* the GIF stream will not be valid. | |
*/ | |
var finish = exports.finish = function finish() { | |
if (!started) return false; | |
var ok = true; | |
started = false; | |
try { | |
out.writeByte(0x3b); // gif trailer | |
closeStream=true; | |
} catch (e) { | |
ok = false; | |
} | |
return ok; | |
}; | |
/** | |
* Resets some members so that a new stream can be started. | |
* This method is actually called by the start method | |
*/ | |
var reset = function reset() { | |
// reset for subsequent use | |
transIndex = 0; | |
image = null; | |
pixels = null; | |
indexedPixels = null; | |
colorTab = null; | |
closeStream = false; | |
firstFrame = true; | |
}; | |
/** | |
* * Sets frame rate in frames per second. Equivalent to | |
* <code>setDelay(1000/fps)</code>. | |
* @param fps | |
* float frame rate (frames per second) | |
*/ | |
var setFrameRate = exports.setFrameRate = function setFrameRate(fps) { | |
if (fps != 0xf) delay = Math.round(100 / fps); | |
}; | |
/** | |
* Sets quality of color quantization (conversion of images to the maximum 256 | |
* colors allowed by the GIF specification). Lower values (minimum = 1) | |
* produce better colors, but slow processing significantly. 10 is the | |
* default, and produces good color mapping at reasonable speeds. Values | |
* greater than 20 do not yield significant improvements in speed. | |
* @param quality | |
* int greater than 0. | |
* @return | |
*/ | |
var setQuality = exports.setQuality = function setQuality(quality) { | |
if (quality < 1) quality = 1; | |
sample = quality; | |
}; | |
/** | |
* Sets the GIF frame size. The default size is the size of the first frame | |
* added if this method is not invoked. | |
* @param w | |
* int frame width. | |
* @param h | |
* int frame width. | |
*/ | |
var setSize = exports.setSize = function setSize(w, h) { | |
if (started && !firstFrame) return; | |
width = w; | |
height = h; | |
if (width < 1) width = 320; | |
if (height < 1) height = 240; | |
sizeSet = true; | |
}; | |
/** | |
* Initiates GIF file creation on the given stream. | |
* @param os | |
* OutputStream on which GIF images are written. | |
* @return false if initial write failed. | |
*/ | |
var start = exports.start = function start() { | |
reset(); | |
var ok = true; | |
closeStream = false; | |
out = new ByteArray(); | |
try { | |
out.writeUTFBytes("GIF89a"); // header | |
} catch (e) { | |
ok = false; | |
} | |
return started = ok; | |
}; | |
var cont = exports.cont = function cont() { | |
reset(); | |
var ok = true; | |
closeStream = false; | |
out = new ByteArray(); | |
return started = ok; | |
}; | |
/** | |
* Analyzes image colors and creates color map. | |
*/ | |
var analyzePixels = function analyzePixels() { | |
var len = pixels.length; | |
var nPix = len / 3; | |
indexedPixels = []; | |
var nq = new NeuQuant(pixels, len, sample); | |
// initialize quantizer | |
colorTab = nq.process(); // create reduced palette | |
// map image pixels to new palette | |
var k = 0; | |
for (var j = 0; j < nPix; j++) { | |
var index = nq.map(pixels[k++] & 0xff, pixels[k++] & 0xff, pixels[k++] & 0xff); | |
usedEntry[index] = true; | |
indexedPixels[j] = index; | |
} | |
pixels = null; | |
colorDepth = 8; | |
palSize = 7; | |
// get closest match to transparent color if specified | |
if (transparent !== null) { | |
transIndex = findClosest(transparent); | |
} | |
}; | |
/** | |
* Returns index of palette color closest to c | |
*/ | |
var findClosest = function findClosest(c) { | |
if (colorTab === null) return -1; | |
var r = (c & 0xFF0000) >> 16; | |
var g = (c & 0x00FF00) >> 8; | |
var b = (c & 0x0000FF); | |
var minpos = 0; | |
var dmin = 256 * 256 * 256; | |
var len = colorTab.length; | |
for (var i = 0; i < len;) { | |
var dr = r - (colorTab[i++] & 0xff); | |
var dg = g - (colorTab[i++] & 0xff); | |
var db = b - (colorTab[i] & 0xff); | |
var d = dr * dr + dg * dg + db * db; | |
var index = i / 3; | |
if (usedEntry[index] && (d < dmin)) { | |
dmin = d; | |
minpos = index; | |
} | |
i++; | |
} | |
return minpos; | |
}; | |
/** | |
* Extracts image pixels into byte array "pixels | |
*/ | |
var getImagePixels = function getImagePixels() { | |
var w = width; | |
var h = height; | |
pixels = []; | |
var data = image; | |
var count = 0; | |
for (var i = 0; i < h; i++) { | |
for (var j = 0; j < w; j++) { | |
var b = (i * w * 4) + j * 4; | |
pixels[count++] = data[b]; | |
pixels[count++] = data[b + 1]; | |
pixels[count++] = data[b + 2]; | |
} | |
} | |
}; | |
/** | |
* Writes Graphic Control Extension | |
*/ | |
var writeGraphicCtrlExt = function writeGraphicCtrlExt() { | |
out.writeByte(0x21); // extension introducer | |
out.writeByte(0xf9); // GCE label | |
out.writeByte(4); // data block size | |
var transp; | |
var disp; | |
if (transparent === null) { | |
transp = 0; | |
disp = 0; // dispose = no action | |
} else { | |
transp = 1; | |
disp = 2; // force clear if using transparent color | |
} | |
if (dispose >= 0) { | |
disp = dispose & 7; // user override | |
} | |
disp <<= 2; | |
// packed fields | |
out.writeByte(0 | // 1:3 reserved | |
disp | // 4:6 disposal | |
0 | // 7 user input - 0 = none | |
transp); // 8 transparency flag | |
WriteShort(delay); // delay x 1/100 sec | |
out.writeByte(transIndex); // transparent color index | |
out.writeByte(0); // block terminator | |
}; | |
/** | |
* Writes Comment Extention | |
*/ | |
var writeCommentExt = function writeCommentExt() { | |
out.writeByte(0x21); // extension introducer | |
out.writeByte(0xfe); // comment label | |
out.writeByte(comment.length); // Block Size (s) | |
out.writeUTFBytes(comment); | |
out.writeByte(0); // block terminator | |
}; | |
/** | |
* Writes Image Descriptor | |
*/ | |
var writeImageDesc = function writeImageDesc() { | |
out.writeByte(0x2c); // image separator | |
WriteShort(0); // image position x,y = 0,0 | |
WriteShort(0); | |
WriteShort(width); // image size | |
WriteShort(height); | |
// packed fields | |
if (firstFrame) { | |
// no LCT - GCT is used for first (or only) frame | |
out.writeByte(0); | |
} else { | |
// specify normal LCT | |
out.writeByte(0x80 | // 1 local color table 1=yes | |
0 | // 2 interlace - 0=no | |
0 | // 3 sorted - 0=no | |
0 | // 4-5 reserved | |
palSize); // 6-8 size of color table | |
} | |
}; | |
/** | |
* Writes Logical Screen Descriptor | |
*/ | |
var writeLSD = function writeLSD() { | |
// logical screen size | |
WriteShort(width); | |
WriteShort(height); | |
// packed fields | |
out.writeByte((0x80 | // 1 : global color table flag = 1 (gct used) | |
0x70 | // 2-4 : color resolution = 7 | |
0x00 | // 5 : gct sort flag = 0 | |
palSize)); // 6-8 : gct size | |
out.writeByte(0); // background color index | |
out.writeByte(0); // pixel aspect ratio - assume 1:1 | |
}; | |
/** | |
* Writes Netscape application extension to define repeat count. | |
*/ | |
var writeNetscapeExt = function writeNetscapeExt() { | |
out.writeByte(0x21); // extension introducer | |
out.writeByte(0xff); // app extension label | |
out.writeByte(11); // block size | |
out.writeUTFBytes("NETSCAPE" + "2.0"); // app id + auth code | |
out.writeByte(3); // sub-block size | |
out.writeByte(1); // loop sub-block id | |
WriteShort(repeat); // loop count (extra iterations, 0=repeat forever) | |
out.writeByte(0); // block terminator | |
}; | |
/** | |
* Writes color table | |
*/ | |
var writePalette = function writePalette() { | |
out.writeBytes(colorTab); | |
var n = (3 * 256) - colorTab.length; | |
for (var i = 0; i < n; i++) out.writeByte(0); | |
}; | |
var WriteShort = function WriteShort(pValue) { | |
out.writeByte(pValue & 0xFF); | |
out.writeByte((pValue >> 8) & 0xFF); | |
}; | |
/** | |
* Encodes and writes pixel data | |
*/ | |
var writePixels = function writePixels() { | |
var myencoder = new LZWEncoder(width, height, indexedPixels, colorDepth); | |
myencoder.encode(out); | |
}; | |
/** | |
* Retrieves the GIF stream | |
*/ | |
var stream = exports.stream = function stream() { | |
return out; | |
}; | |
var setProperties = exports.setProperties = function setProperties(has_start, is_first) { | |
started = has_start; | |
firstFrame = is_first; | |
}; | |
return exports; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/seedrandom/2.4.3/seedrandom.min.js"></script> | |
<script src="https://d3js.org/d3-random.v1.min.js"></script> | |
<script language="javascript" type="text/javascript" src="z_purview_helper.js"></script> | |
<script language="javascript" type="text/javascript" src="z_focused_random.js"></script> | |
<script language="javascript" type="text/javascript" src="p5.func.js"></script> | |
<script type="text/javascript" src="LZWEncoder.js"></script> | |
<script type="text/javascript" src="NeuQuant.js"></script> | |
<script type="text/javascript" src="GIFEncoder.js"></script> | |
<script type="text/javascript" src="b64.js"></script> | |
<script type="text/javascript" src="z_recorder.js"></script> | |
<script language="javascript" type="text/javascript" src="simplex-noise.js"></script> | |
<script language="javascript" type="text/javascript" src="canvas_size_normal.js"></script> | |
<script language="javascript" type="text/javascript" src="code.js"></script> | |
<style> body {padding: 0; margin: 0;} </style> | |
</head> | |
<body style="background-color:white"> | |
<div class="outer"> | |
<div class="inner"> | |
<div id="canvasContainer"></div> | |
</div> | |
</div> | |
</table> | |
<br> | |
<a href="sketch.html">sketch</a><br> | |
<a href="code.js">source code</a><br> | |
<a href="preview.jpg">preview image</a><br> | |
<a href="code_l.html">(large)</a><br> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* This class handles LZW encoding | |
* Adapted from Jef Poskanzer's Java port by way of J. M. G. Elliott. | |
* @author Kevin Weiner (original Java version - [email protected]) | |
* @author Thibault Imbert (AS3 version - bytearray.org) | |
* @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) | |
* @version 0.1 AS3 implementation | |
*/ | |
LZWEncoder = function() { | |
var exports = {}; | |
var EOF = -1; | |
var imgW; | |
var imgH; | |
var pixAry; | |
var initCodeSize; | |
var remaining; | |
var curPixel; | |
// GIFCOMPR.C - GIF Image compression routines | |
// Lempel-Ziv compression based on 'compress'. GIF modifications by | |
// David Rowley ([email protected]) | |
// General DEFINEs | |
var BITS = 12; | |
var HSIZE = 5003; // 80% occupancy | |
// GIF Image compression - modified 'compress' | |
// Based on: compress.c - File compression ala IEEE Computer, June 1984. | |
// By Authors: Spencer W. Thomas (decvax!harpo!utah-cs!utah-gr!thomas) | |
// Jim McKie (decvax!mcvax!jim) | |
// Steve Davies (decvax!vax135!petsd!peora!srd) | |
// Ken Turkowski (decvax!decwrl!turtlevax!ken) | |
// James A. Woods (decvax!ihnp4!ames!jaw) | |
// Joe Orost (decvax!vax135!petsd!joe) | |
var n_bits; // number of bits/code | |
var maxbits = BITS; // user settable max # bits/code | |
var maxcode; // maximum code, given n_bits | |
var maxmaxcode = 1 << BITS; // should NEVER generate this code | |
var htab = []; | |
var codetab = []; | |
var hsize = HSIZE; // for dynamic table sizing | |
var free_ent = 0; // first unused entry | |
// block compression parameters -- after all codes are used up, | |
// and compression rate changes, start over. | |
var clear_flg = false; | |
// Algorithm: use open addressing double hashing (no chaining) on the | |
// prefix code / next character combination. We do a variant of Knuth's | |
// algorithm D (vol. 3, sec. 6.4) along with G. Knott's relatively-prime | |
// secondary probe. Here, the modular division first probe is gives way | |
// to a faster exclusive-or manipulation. Also do block compression with | |
// an adaptive reset, whereby the code table is cleared when the compression | |
// ratio decreases, but after the table fills. The variable-length output | |
// codes are re-sized at this point, and a special CLEAR code is generated | |
// for the decompressor. Late addition: construct the table according to | |
// file size for noticeable speed improvement on small files. Please direct | |
// questions about this implementation to ames!jaw. | |
var g_init_bits; | |
var ClearCode; | |
var EOFCode; | |
// output | |
// Output the given code. | |
// Inputs: | |
// code: A n_bits-bit integer. If == -1, then EOF. This assumes | |
// that n_bits =< wordsize - 1. | |
// Outputs: | |
// Outputs code to the file. | |
// Assumptions: | |
// Chars are 8 bits long. | |
// Algorithm: | |
// Maintain a BITS character long buffer (so that 8 codes will | |
// fit in it exactly). Use the VAX insv instruction to insert each | |
// code in turn. When the buffer fills up empty it and start over. | |
var cur_accum = 0; | |
var cur_bits = 0; | |
var masks = [0x0000, 0x0001, 0x0003, 0x0007, 0x000F, 0x001F, 0x003F, 0x007F, 0x00FF, 0x01FF, 0x03FF, 0x07FF, 0x0FFF, 0x1FFF, 0x3FFF, 0x7FFF, 0xFFFF]; | |
// Number of characters so far in this 'packet' | |
var a_count; | |
// Define the storage for the packet accumulator | |
var accum = []; | |
var LZWEncoder = exports.LZWEncoder = function LZWEncoder(width, height, pixels, color_depth) { | |
imgW = width; | |
imgH = height; | |
pixAry = pixels; | |
initCodeSize = Math.max(2, color_depth); | |
}; | |
// Add a character to the end of the current packet, and if it is 254 | |
// characters, flush the packet to disk. | |
var char_out = function char_out(c, outs) { | |
accum[a_count++] = c; | |
if (a_count >= 254) flush_char(outs); | |
}; | |
// Clear out the hash table | |
// table clear for block compress | |
var cl_block = function cl_block(outs) { | |
cl_hash(hsize); | |
free_ent = ClearCode + 2; | |
clear_flg = true; | |
output(ClearCode, outs); | |
}; | |
// reset code table | |
var cl_hash = function cl_hash(hsize) { | |
for (var i = 0; i < hsize; ++i) htab[i] = -1; | |
}; | |
var compress = exports.compress = function compress(init_bits, outs) { | |
var fcode; | |
var i; /* = 0 */ | |
var c; | |
var ent; | |
var disp; | |
var hsize_reg; | |
var hshift; | |
// Set up the globals: g_init_bits - initial number of bits | |
g_init_bits = init_bits; | |
// Set up the necessary values | |
clear_flg = false; | |
n_bits = g_init_bits; | |
maxcode = MAXCODE(n_bits); | |
ClearCode = 1 << (init_bits - 1); | |
EOFCode = ClearCode + 1; | |
free_ent = ClearCode + 2; | |
a_count = 0; // clear packet | |
ent = nextPixel(); | |
hshift = 0; | |
for (fcode = hsize; fcode < 65536; fcode *= 2) | |
++hshift; | |
hshift = 8 - hshift; // set hash code range bound | |
hsize_reg = hsize; | |
cl_hash(hsize_reg); // clear hash table | |
output(ClearCode, outs); | |
outer_loop: while ((c = nextPixel()) != EOF) { | |
fcode = (c << maxbits) + ent; | |
i = (c << hshift) ^ ent; // xor hashing | |
if (htab[i] == fcode) { | |
ent = codetab[i]; | |
continue; | |
} | |
else if (htab[i] >= 0) { // non-empty slot | |
disp = hsize_reg - i; // secondary hash (after G. Knott) | |
if (i === 0) disp = 1; | |
do { | |
if ((i -= disp) < 0) | |
i += hsize_reg; | |
if (htab[i] == fcode) { | |
ent = codetab[i]; | |
continue outer_loop; | |
} | |
} while (htab[i] >= 0); | |
} | |
output(ent, outs); | |
ent = c; | |
if (free_ent < maxmaxcode) { | |
codetab[i] = free_ent++; // code -> hashtable | |
htab[i] = fcode; | |
} | |
else cl_block(outs); | |
} | |
// Put out the final code. | |
output(ent, outs); | |
output(EOFCode, outs); | |
}; | |
// ---------------------------------------------------------------------------- | |
var encode = exports.encode = function encode(os) { | |
os.writeByte(initCodeSize); // write "initial code size" byte | |
remaining = imgW * imgH; // reset navigation variables | |
curPixel = 0; | |
compress(initCodeSize + 1, os); // compress and write the pixel data | |
os.writeByte(0); // write block terminator | |
}; | |
// Flush the packet to disk, and reset the accumulator | |
var flush_char = function flush_char(outs) { | |
if (a_count > 0) { | |
outs.writeByte(a_count); | |
outs.writeBytes(accum, 0, a_count); | |
a_count = 0; | |
} | |
}; | |
var MAXCODE = function MAXCODE(n_bits) { | |
return (1 << n_bits) - 1; | |
}; | |
// ---------------------------------------------------------------------------- | |
// Return the next pixel from the image | |
// ---------------------------------------------------------------------------- | |
var nextPixel = function nextPixel() { | |
if (remaining === 0) return EOF; | |
--remaining; | |
var pix = pixAry[curPixel++]; | |
return pix & 0xff; | |
}; | |
var output = function output(code, outs) { | |
cur_accum &= masks[cur_bits]; | |
if (cur_bits > 0) cur_accum |= (code << cur_bits); | |
else cur_accum = code; | |
cur_bits += n_bits; | |
while (cur_bits >= 8) { | |
char_out((cur_accum & 0xff), outs); | |
cur_accum >>= 8; | |
cur_bits -= 8; | |
} | |
// If the next entry is going to be too big for the code size, | |
// then increase it, if possible. | |
if (free_ent > maxcode || clear_flg) { | |
if (clear_flg) { | |
maxcode = MAXCODE(n_bits = g_init_bits); | |
clear_flg = false; | |
} else { | |
++n_bits; | |
if (n_bits == maxbits) maxcode = maxmaxcode; | |
else maxcode = MAXCODE(n_bits); | |
} | |
} | |
if (code == EOFCode) { | |
// At EOF, write the rest of the buffer. | |
while (cur_bits > 0) { | |
char_out((cur_accum & 0xff), outs); | |
cur_accum >>= 8; | |
cur_bits -= 8; | |
} | |
flush_char(outs); | |
} | |
}; | |
LZWEncoder.apply(this, arguments); | |
return exports; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* NeuQuant Neural-Net Quantization Algorithm | |
* ------------------------------------------ | |
* | |
* Copyright (c) 1994 Anthony Dekker | |
* | |
* NEUQUANT Neural-Net quantization algorithm by Anthony Dekker, 1994. See | |
* "Kohonen neural networks for optimal colour quantization" in "Network: | |
* Computation in Neural Systems" Vol. 5 (1994) pp 351-367. for a discussion of | |
* the algorithm. | |
* | |
* Any party obtaining a copy of these files from the author, directly or | |
* indirectly, is granted, free of charge, a full and unrestricted irrevocable, | |
* world-wide, paid up, royalty-free, nonexclusive right and license to deal in | |
* this software and documentation files (the "Software"), including without | |
* limitation the rights to use, copy, modify, merge, publish, distribute, | |
* sublicense, and/or sell copies of the Software, and to permit persons who | |
* receive copies from any such party to do so, with the only requirement being | |
* that this copyright notice remain intact. | |
*/ | |
/* | |
* This class handles Neural-Net quantization algorithm | |
* @author Kevin Weiner (original Java version - [email protected]) | |
* @author Thibault Imbert (AS3 version - bytearray.org) | |
* @author Kevin Kwok (JavaScript version - https://github.com/antimatter15/jsgif) | |
* @version 0.1 AS3 implementation | |
*/ | |
NeuQuant = function() { | |
var exports = {}; | |
var netsize = 256; /* number of colours used */ | |
/* four primes near 500 - assume no image has a length so large */ | |
/* that it is divisible by all four primes */ | |
var prime1 = 499; | |
var prime2 = 491; | |
var prime3 = 487; | |
var prime4 = 503; | |
var minpicturebytes = (3 * prime4); /* minimum size for input image */ | |
/* | |
* Program Skeleton ---------------- [select samplefac in range 1..30] [read | |
* image from input file] pic = (unsigned char*) malloc(3*width*height); | |
* initnet(pic,3*width*height,samplefac); learn(); unbiasnet(); [write output | |
* image header, using writecolourmap(f)] inxbuild(); write output image using | |
* inxsearch(b,g,r) | |
*/ | |
/* | |
* Network Definitions ------------------- | |
*/ | |
var maxnetpos = (netsize - 1); | |
var netbiasshift = 4; /* bias for colour values */ | |
var ncycles = 100; /* no. of learning cycles */ | |
/* defs for freq and bias */ | |
var intbiasshift = 16; /* bias for fractions */ | |
var intbias = (1 << intbiasshift); | |
var gammashift = 10; /* gamma = 1024 */ | |
var gamma = (1 << gammashift); | |
var betashift = 10; | |
var beta = (intbias >> betashift); /* beta = 1/1024 */ | |
var betagamma = (intbias << (gammashift - betashift)); | |
/* defs for decreasing radius factor */ | |
var initrad = (netsize >> 3); /* for 256 cols, radius starts */ | |
var radiusbiasshift = 6; /* at 32.0 biased by 6 bits */ | |
var radiusbias = (1 << radiusbiasshift); | |
var initradius = (initrad * radiusbias); /* and decreases by a */ | |
var radiusdec = 30; /* factor of 1/30 each cycle */ | |
/* defs for decreasing alpha factor */ | |
var alphabiasshift = 10; /* alpha starts at 1.0 */ | |
var initalpha = (1 << alphabiasshift); | |
var alphadec; /* biased by 10 bits */ | |
/* radbias and alpharadbias used for radpower calculation */ | |
var radbiasshift = 8; | |
var radbias = (1 << radbiasshift); | |
var alpharadbshift = (alphabiasshift + radbiasshift); | |
var alpharadbias = (1 << alpharadbshift); | |
/* | |
* Types and Global Variables -------------------------- | |
*/ | |
var thepicture; /* the input image itself */ | |
var lengthcount; /* lengthcount = H*W*3 */ | |
var samplefac; /* sampling factor 1..30 */ | |
// typedef int pixel[4]; /* BGRc */ | |
var network; /* the network itself - [netsize][4] */ | |
var netindex = []; | |
/* for network lookup - really 256 */ | |
var bias = []; | |
/* bias and freq arrays for learning */ | |
var freq = []; | |
var radpower = []; | |
var NeuQuant = exports.NeuQuant = function NeuQuant(thepic, len, sample) { | |
var i; | |
var p; | |
thepicture = thepic; | |
lengthcount = len; | |
samplefac = sample; | |
network = new Array(netsize); | |
for (i = 0; i < netsize; i++) { | |
network[i] = new Array(4); | |
p = network[i]; | |
p[0] = p[1] = p[2] = (i << (netbiasshift + 8)) / netsize; | |
freq[i] = intbias / netsize; /* 1/netsize */ | |
bias[i] = 0; | |
} | |
}; | |
var colorMap = function colorMap() { | |
var map = []; | |
var index = new Array(netsize); | |
for (var i = 0; i < netsize; i++) | |
index[network[i][3]] = i; | |
var k = 0; | |
for (var l = 0; l < netsize; l++) { | |
var j = index[l]; | |
map[k++] = (network[j][0]); | |
map[k++] = (network[j][1]); | |
map[k++] = (network[j][2]); | |
} | |
return map; | |
}; | |
/* | |
* Insertion sort of network and building of netindex[0..255] (to do after | |
* unbias) | |
* ------------------------------------------------------------------------------- | |
*/ | |
var inxbuild = function inxbuild() { | |
var i; | |
var j; | |
var smallpos; | |
var smallval; | |
var p; | |
var q; | |
var previouscol; | |
var startpos; | |
previouscol = 0; | |
startpos = 0; | |
for (i = 0; i < netsize; i++) { | |
p = network[i]; | |
smallpos = i; | |
smallval = p[1]; /* index on g */ | |
/* find smallest in i..netsize-1 */ | |
for (j = i + 1; j < netsize; j++) { | |
q = network[j]; | |
if (q[1] < smallval) { /* index on g */ | |
smallpos = j; | |
smallval = q[1]; /* index on g */ | |
} | |
} | |
q = network[smallpos]; | |
/* swap p (i) and q (smallpos) entries */ | |
if (i != smallpos) { | |
j = q[0]; | |
q[0] = p[0]; | |
p[0] = j; | |
j = q[1]; | |
q[1] = p[1]; | |
p[1] = j; | |
j = q[2]; | |
q[2] = p[2]; | |
p[2] = j; | |
j = q[3]; | |
q[3] = p[3]; | |
p[3] = j; | |
} | |
/* smallval entry is now in position i */ | |
if (smallval != previouscol) { | |
netindex[previouscol] = (startpos + i) >> 1; | |
for (j = previouscol + 1; j < smallval; j++) netindex[j] = i; | |
previouscol = smallval; | |
startpos = i; | |
} | |
} | |
netindex[previouscol] = (startpos + maxnetpos) >> 1; | |
for (j = previouscol + 1; j < 256; j++) netindex[j] = maxnetpos; /* really 256 */ | |
}; | |
/* | |
* Main Learning Loop ------------------ | |
*/ | |
var learn = function learn() { | |
var i; | |
var j; | |
var b; | |
var g; | |
var r; | |
var radius; | |
var rad; | |
var alpha; | |
var step; | |
var delta; | |
var samplepixels; | |
var p; | |
var pix; | |
var lim; | |
if (lengthcount < minpicturebytes) samplefac = 1; | |
alphadec = 30 + ((samplefac - 1) / 3); | |
p = thepicture; | |
pix = 0; | |
lim = lengthcount; | |
samplepixels = lengthcount / (3 * samplefac); | |
delta = (samplepixels / ncycles) | 0; | |
alpha = initalpha; | |
radius = initradius; | |
rad = radius >> radiusbiasshift; | |
if (rad <= 1) rad = 0; | |
for (i = 0; i < rad; i++) radpower[i] = alpha * (((rad * rad - i * i) * radbias) / (rad * rad)); | |
if (lengthcount < minpicturebytes) step = 3; | |
else if ((lengthcount % prime1) !== 0) step = 3 * prime1; | |
else { | |
if ((lengthcount % prime2) !== 0) step = 3 * prime2; | |
else { | |
if ((lengthcount % prime3) !== 0) step = 3 * prime3; | |
else step = 3 * prime4; | |
} | |
} | |
i = 0; | |
while (i < samplepixels) { | |
b = (p[pix + 0] & 0xff) << netbiasshift; | |
g = (p[pix + 1] & 0xff) << netbiasshift; | |
r = (p[pix + 2] & 0xff) << netbiasshift; | |
j = contest(b, g, r); | |
altersingle(alpha, j, b, g, r); | |
if (rad !== 0) alterneigh(rad, j, b, g, r); /* alter neighbours */ | |
pix += step; | |
if (pix >= lim) pix -= lengthcount; | |
i++; | |
if (delta === 0) delta = 1; | |
if (i % delta === 0) { | |
alpha -= alpha / alphadec; | |
radius -= radius / radiusdec; | |
rad = radius >> radiusbiasshift; | |
if (rad <= 1) rad = 0; | |
for (j = 0; j < rad; j++) radpower[j] = alpha * (((rad * rad - j * j) * radbias) / (rad * rad)); | |
} | |
} | |
}; | |
/* | |
** Search for BGR values 0..255 (after net is unbiased) and return colour | |
* index | |
* ---------------------------------------------------------------------------- | |
*/ | |
var map = exports.map = function map(b, g, r) { | |
var i; | |
var j; | |
var dist; | |
var a; | |
var bestd; | |
var p; | |
var best; | |
bestd = 1000; /* biggest possible dist is 256*3 */ | |
best = -1; | |
i = netindex[g]; /* index on g */ | |
j = i - 1; /* start at netindex[g] and work outwards */ | |
while ((i < netsize) || (j >= 0)) { | |
if (i < netsize) { | |
p = network[i]; | |
dist = p[1] - g; /* inx key */ | |
if (dist >= bestd) i = netsize; /* stop iter */ | |
else { | |
i++; | |
if (dist < 0) dist = -dist; | |
a = p[0] - b; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
a = p[2] - r; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
bestd = dist; | |
best = p[3]; | |
} | |
} | |
} | |
} | |
if (j >= 0) { | |
p = network[j]; | |
dist = g - p[1]; /* inx key - reverse dif */ | |
if (dist >= bestd) j = -1; /* stop iter */ | |
else { | |
j--; | |
if (dist < 0) dist = -dist; | |
a = p[0] - b; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
a = p[2] - r; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
bestd = dist; | |
best = p[3]; | |
} | |
} | |
} | |
} | |
} | |
return (best); | |
}; | |
var process = exports.process = function process() { | |
learn(); | |
unbiasnet(); | |
inxbuild(); | |
return colorMap(); | |
}; | |
/* | |
* Unbias network to give byte values 0..255 and record position i to prepare | |
* for sort | |
* ----------------------------------------------------------------------------------- | |
*/ | |
var unbiasnet = function unbiasnet() { | |
var i; | |
var j; | |
for (i = 0; i < netsize; i++) { | |
network[i][0] >>= netbiasshift; | |
network[i][1] >>= netbiasshift; | |
network[i][2] >>= netbiasshift; | |
network[i][3] = i; /* record colour no */ | |
} | |
}; | |
/* | |
* Move adjacent neurons by precomputed alpha*(1-((i-j)^2/[r]^2)) in | |
* radpower[|i-j|] | |
* --------------------------------------------------------------------------------- | |
*/ | |
var alterneigh = function alterneigh(rad, i, b, g, r) { | |
var j; | |
var k; | |
var lo; | |
var hi; | |
var a; | |
var m; | |
var p; | |
lo = i - rad; | |
if (lo < -1) lo = -1; | |
hi = i + rad; | |
if (hi > netsize) hi = netsize; | |
j = i + 1; | |
k = i - 1; | |
m = 1; | |
while ((j < hi) || (k > lo)) { | |
a = radpower[m++]; | |
if (j < hi) { | |
p = network[j++]; | |
try { | |
p[0] -= (a * (p[0] - b)) / alpharadbias; | |
p[1] -= (a * (p[1] - g)) / alpharadbias; | |
p[2] -= (a * (p[2] - r)) / alpharadbias; | |
} catch (e) {} // prevents 1.3 miscompilation | |
} | |
if (k > lo) { | |
p = network[k--]; | |
try { | |
p[0] -= (a * (p[0] - b)) / alpharadbias; | |
p[1] -= (a * (p[1] - g)) / alpharadbias; | |
p[2] -= (a * (p[2] - r)) / alpharadbias; | |
} catch (e) {} | |
} | |
} | |
}; | |
/* | |
* Move neuron i towards biased (b,g,r) by factor alpha | |
* ---------------------------------------------------- | |
*/ | |
var altersingle = function altersingle(alpha, i, b, g, r) { | |
/* alter hit neuron */ | |
var n = network[i]; | |
n[0] -= (alpha * (n[0] - b)) / initalpha; | |
n[1] -= (alpha * (n[1] - g)) / initalpha; | |
n[2] -= (alpha * (n[2] - r)) / initalpha; | |
}; | |
/* | |
* Search for biased BGR values ---------------------------- | |
*/ | |
var contest = function contest(b, g, r) { | |
/* finds closest neuron (min dist) and updates freq */ | |
/* finds best neuron (min dist-bias) and returns position */ | |
/* for frequently chosen neurons, freq[i] is high and bias[i] is negative */ | |
/* bias[i] = gamma*((1/netsize)-freq[i]) */ | |
var i; | |
var dist; | |
var a; | |
var biasdist; | |
var betafreq; | |
var bestpos; | |
var bestbiaspos; | |
var bestd; | |
var bestbiasd; | |
var n; | |
bestd = ~ (1 << 31); | |
bestbiasd = bestd; | |
bestpos = -1; | |
bestbiaspos = bestpos; | |
for (i = 0; i < netsize; i++) { | |
n = network[i]; | |
dist = n[0] - b; | |
if (dist < 0) dist = -dist; | |
a = n[1] - g; | |
if (a < 0) a = -a; | |
dist += a; | |
a = n[2] - r; | |
if (a < 0) a = -a; | |
dist += a; | |
if (dist < bestd) { | |
bestd = dist; | |
bestpos = i; | |
} | |
biasdist = dist - ((bias[i]) >> (intbiasshift - netbiasshift)); | |
if (biasdist < bestbiasd) { | |
bestbiasd = biasdist; | |
bestbiaspos = i; | |
} | |
betafreq = (freq[i] >> betashift); | |
freq[i] -= betafreq; | |
bias[i] += (betafreq << gammashift); | |
} | |
freq[bestpos] += beta; | |
bias[bestpos] -= betagamma; | |
return (bestbiaspos); | |
}; | |
NeuQuant.apply(this, arguments); | |
return exports; | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! p5.func.js v0.0.1 2017-05-27 */ | |
/** | |
* @module p5.func | |
* @submodule p5.func | |
* @for p5.func | |
* @main | |
*/ | |
/** | |
* p5.func | |
* R. Luke DuBois ([email protected]) | |
* Integrated Digital Media / Brooklyn Experimental Media Center | |
* New York University | |
* The MIT License (MIT). | |
* | |
* https://github.com/IDMNYU/p5.js-func | |
* | |
* the p5.func module contains five new objects for extending p5.js : | |
* p5.Gen() : function generators (waveforms, curves, window functions, noise, etc.) | |
* p5.Ease() : easing / interpolation functions | |
* p5.ArrayEval() : equation evaluator to generate pre-computed arrays | |
* p5.Filt() : biquadratic filter object | |
* p5.FastFourierTransform() : signal neutral FFT implementation | |
* | |
* p5.func also contains some miscellaneous functions: | |
* imap() : constrainted integer mapping function | |
* wrap() : wrapping function | |
* fold() : folding function | |
* createArray()/normalizeArray()/resizeArray()/multiplyArray()/addArray()/subtractArray()/divideArray()/moduloArray()/sumArray() : array functions | |
* f2ib() / ib2f() : int<->float coercion with bit parity | |
* sinc() : sinc (sinus cardinalis) function | |
* besselI0() : Bessel function | |
* fplot() : formattable console plot of any array | |
* | |
* primary sources: | |
* RTcmix Scorefile Commands: http://rtcmix.org/reference/scorefile/ | |
* Robert Penner's Easing Functions: http://robertpenner.com/easing/ | |
* Golan Levin's Pattern Master: https://github.com/golanlevin/Pattern_Master | |
* Robert Bristow-Johnson's Audio EQ Cookbook: http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt | |
* Corban Brook's dsp.js: https://github.com/corbanbrook/dsp.js/ | |
*/ | |
(function (root, factory) { | |
if (typeof define === 'function' && define.amd) | |
define('p5.func', ['p5'], function (p5) { (factory(p5));}); | |
else if (typeof exports === 'object') | |
factory(require('../p5')); | |
else | |
factory(root['p5']); | |
}(this, function (p5) { | |
// ============================================================================= | |
// p5.Gen | |
// ============================================================================= | |
/** | |
* Base class for a function generator | |
* | |
* @class p5.Gen | |
* @constructor | |
*/ | |
p5.Gen = function() { | |
// | |
// this object implements GEN table-style | |
// algorithms adapted from MUSICN languages | |
// (e.g. Csound, RTcmix, ChucK, Supercollider, Max). | |
// these algorithms are solved by direct (0.-1.) | |
// evaluation with utility functions to compute arrays. | |
// | |
// some code below is adapted from RTcmix : | |
// https://github.com/RTcmix/RTcmix | |
// Copyright (C) 2005 The RTcmix Development Team, released under the Apache 2.0 License | |
// | |
this.version = 0.01; // just some crap for constructor | |
var that = this; // some bullshit | |
}; // end p5.Gen constructor | |
// harmonic / periodic wave from a list of partial strengths. | |
// Csound / RTcmix GEN10, ported from RTcmix. | |
p5.Gen.prototype.harmonics = function(_x, _args) { | |
var u = true; // single value? | |
if(!Array.isArray(_x) && _x.constructor !== Float32Array && _x.constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
if(!Array.isArray(_args)) _args = [_args]; // catch single harmonic | |
var _sum; // match type: | |
if(Array.isArray(_x)) _sum = new Array(_x.length); | |
else if(_x.constructor === Float32Array) _sum = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _sum = new Float64Array(_x.length); | |
for(var i in _x) { | |
var j = _args.length; | |
_sum[i] = 0.0; | |
while (j--) { | |
if (_args[j] != 0) { | |
var _val = TWO_PI * _x[i] * (j + 1); | |
_sum[i] += (sin(_val) * _args[j]); | |
} | |
} | |
} | |
return(u ? _sum : _sum[0]); | |
}; | |
// wave from triples (ratio, amp, phase). | |
// Csound / RTcmix GEN09, ported from RTcmix. | |
p5.Gen.prototype.triples = function(_x, _args) { | |
var u = true; // single value? | |
if(!Array.isArray(_x) && _x.constructor !== Float32Array && _x.constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
if(_args.length<2) { | |
console.log("p5.Gen : we need at least 3 arguments!") | |
return(0.); | |
} | |
else if(_args.length%3!=0) { | |
console.log("p5.Gen : incomplete <partial, amp, phase> triplet!"); | |
} | |
var _sum; // match type: | |
if(Array.isArray(_x)) _sum = new Array(_x.length); | |
else if(_x.constructor === Float32Array) _sum = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _sum = new Float64Array(_x.length); | |
for(i in _x) { | |
_sum[i] = 0.0; | |
for (var j = _args.length - 1; j > 0; j -= 3) { | |
if (_args[j - 1] != 0.0) { | |
var val; | |
if (_args[j - 2] == 0.0) val = 1.0; // BGG: harmonic 0 (DC) | |
else { | |
val = sin(TWO_PI * (_x[i] / (1.0 / _args[j - 2]) + _args[j] / 360.0)); | |
} | |
_sum[i] += (val * _args[j - 1]); | |
} | |
} | |
} | |
return(u ? _sum : _sum[0]); | |
}; | |
// transfer function from chebyshev polynomials. | |
// Csound / RTcmix GEN17, ported from RTcmix. | |
p5.Gen.prototype.chebyshev = function(_x, _args) { | |
var u = true; // single value? | |
if(!Array.isArray(_x) && _x.constructor !== Float32Array && _x.constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
if(!Array.isArray(_args)) _args = [_args]; // catch single value | |
// compute the transfer function using the chebyshev equation... | |
var _sum // match type: | |
if(Array.isArray(_x)) _sum = new Array(_x.length); | |
else if(_x.constructor === Float32Array) _sum = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _sum = new Float64Array(_x.length); | |
for(var i in _x) { | |
var v=_x[i]*2.-1.; | |
_sum[i]=0.; | |
var Tn1=1; | |
var Tn=v; | |
for(var j=0; j<_args.length;j++) { | |
_sum[i]+=_args[j]*Tn; | |
Tn2=Tn1; | |
Tn1=Tn; | |
Tn=2*v*Tn1-Tn2; | |
} | |
} | |
return(u ? _sum : _sum[0]); | |
} | |
// linear breakpoint function (time, value pairs with y normalization). | |
// Csound GEN27 / RTcmix GEN24, rewritten by rld. | |
p5.Gen.prototype.bpf = function(_x, _args) { | |
var u = true; // single value? | |
if(!Array.isArray(_x) && _x.constructor !== Float32Array && _x.constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
if(_args.length%2!=0) { | |
console.log("p5.Gen : incomplete <time, value> pair!") | |
return(0.); | |
} | |
var endtime = _args[_args.length - 2]; | |
var starttime = _args[0]; | |
if (endtime - starttime <= 0.0) { | |
console.log("p5.Gen : bpf times must be in ascending order!"); | |
return(0.); | |
} | |
var scaler = 1.0 / (endtime - starttime); | |
var thistime = 0; | |
var nexttime = 0; | |
var thisval = 0; | |
var nextval = 0; | |
var _y // match type: | |
if(Array.isArray(_x)) _y = new Array(_x.length); | |
else if(_x.constructor === Float32Array) _y = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _y = new Float64Array(_x.length); | |
for(i in _x) | |
{ | |
for (var k = 1; k < _args.length; k += 2) { | |
thistime = _args[k-1]*scaler; | |
thisval = _args[k]; | |
if(k<_args.length-1) { | |
nexttime = _args[k+1]*scaler; | |
nextval = _args[k+2]; | |
} | |
else { | |
nexttime = thistime; | |
nextval = thisval; | |
} | |
if(nexttime - thistime < 0.0) { // okay for them to be the same | |
console.log("p5.Gen : bpf times music be in ascending order!"); | |
return(0.); | |
} | |
if(_x[i]>=thistime && _x[i]<=nexttime) // this point in bpf | |
{ | |
var _thisval = _args[k+1]; | |
_y[i] = map(_x[i], thistime, nexttime, thisval, nextval); | |
break; | |
} | |
} | |
} | |
return(u ? _y : _y[0]); | |
} | |
// common random number distributions. | |
// Csound GEN21 / RTcmix GEN20, written by rld. | |
// if no seed, auto-generated from millis(). | |
// algorithms adapted from dodge and jerse (1985). | |
// inspired by denis lorrain's | |
// 'a panoply of stochastic cannons' (1980): | |
// http://www.jstor.org/stable/3679442 | |
p5.Gen.prototype.random = function(_x, _type) { | |
// distributions based on RTcmix GEN20: | |
// even distribution ["even" or "linear"] | |
// low weighted linear distribution ["low"] | |
// high weighted linear distribution ["high"] | |
// triangle linear distribution ["triangle"] | |
// gaussian distribution ["gaussian"] | |
// cauchy distribution ["cauchy"] | |
// | |
// a -1 in the seed parameter (or missing) will | |
// instruct the algorithm to use the millisecond | |
// clock for a random seed. | |
var u = true; // single value? | |
var _s = 0.; | |
if(!_x) // no arguments, so do linear with random seed | |
{ | |
_type = 'linear'; | |
_x = [millis()]; | |
u = false; | |
} | |
else if(typeof(_x)!='string') // first argument is a number, so seed | |
{ | |
if(!Array.isArray(arguments[0]) && arguments[0].constructor !== Float32Array && arguments[0].constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
} | |
else // argument is a string, so type | |
{ | |
_type=_x; // it's the type, not the seed | |
_x = [millis()]; // random seed | |
u = false; | |
} | |
var _v; // match type: | |
if(Array.isArray(_x)) _v= new Array(_x.length); | |
else if(_x.constructor === Float32Array) _v = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _v = new Float64Array(_x.length); | |
if(_x[0]===-1) randomSeed(millis()*100000.); | |
for(var i in _x) | |
{ | |
if(_x[i]!=-1) randomSeed(_x[i]*100000.); | |
switch(_type) { | |
case "linear": | |
case "even": | |
_v[i] = random(); | |
break; | |
case "low": | |
_v[i] = min(random(), random()); | |
break; | |
case "high": | |
_v[i] = max(random(), random()); | |
break; | |
case "triangle": | |
_v[i] = (random()+random())/2.0; | |
break; | |
case "gaussian": | |
var n = 12; | |
var sigma = 0.166666; | |
var randnum = 0.0; | |
for (var j = 0; j < n; j++) { | |
randnum += random(); | |
} | |
_v[i] = sigma * (randnum - n/2) + 0.5; | |
break; | |
case "cauchy": | |
var alpha = 0.00628338; | |
do { | |
do { | |
_v[i] = random(); | |
} while (_v[i] == 0.5); | |
_v[i] = (alpha * tan(_v[i] * PI)) + 0.5; | |
} while (_v[i] < 0.0 || _v[i] > 1.0); | |
break; | |
default: | |
_v[i] = random(); | |
break; | |
} | |
} | |
return(u ? _v : _v[0]); | |
} | |
// common window functions for signal processing. | |
// Csound GEN20 / RTcmix GEN25 (paris smaragdis / dave topper) | |
// and Pattern Master by @golanlevin . | |
// rewritten / ported from C and Java by rld. | |
// equations from Wikipedia: https://en.wikipedia.org/wiki/Window_function | |
p5.Gen.prototype.window = function(_x, _type, _args) { | |
// flag order based on CSOUND GEN20: | |
// 1 = hamming | |
// 2 = hanning | |
// 3 = bartlett (triangle) | |
// 4 = blackman (3-term) | |
// 5 = blackman-harris (4-term) | |
// 6 = gaussian | |
// 7 = kaiser | |
// 8 = rectangle | |
// 9 = sinc | |
// these and others are addressible by name. | |
var u = true; // single value? | |
if(!Array.isArray(_args)) _args = [_args]; // catch single value | |
if(!Array.isArray(_x) && _x.constructor !== Float32Array && _x.constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
var _y; // match type: | |
if(Array.isArray(_x)) _y= new Array(_x.length); | |
else if(_x.constructor === Float32Array) _y = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _y = new Float64Array(_x.length); | |
var i; | |
switch(_type) { | |
// proposed by richard wesley hamming (1915-1998). | |
// optimized to quash the nearest sidelobe. | |
case 1: | |
case "hamming": | |
var alpha = 0.54; | |
var beta = 0.46; | |
for(i in _x) _y[i] = alpha - beta * cos(TWO_PI * _x[i]); | |
break; | |
// named for julius von hann (1839-1921). | |
// sidelobes fall at 18db/oct. | |
case 2: | |
case "hanning": // not the guy's actual name | |
case "vonhann": // the guy's actual name | |
case "hann": // sort of the guy's actual name | |
case "hannsolo": // no | |
case "hanningvonhannmeister": // very much no | |
for(i in _x) _y[i] = 0.5 * (1-cos(TWO_PI*_x[i])); | |
break; | |
// proposed by m.s. bartlett (1910-2002). | |
// also by lipót (leopold) fejér (1880-1959). | |
// triangular (2nd order b-spline) window. | |
case 3: | |
case "bartlett": | |
case "fejer": | |
case "fejér": // get the accent right | |
case "triangle": | |
for(i in _x) _y[i] = 1.0 - abs((_x[i]-0.5)/0.5); | |
break; | |
// bartlett-hann window. | |
case "bartlett-hann": | |
var a0 = 0.62; | |
var a1 = 0.48; | |
var a2 = 0.38; | |
for(i in _x) _y[i] = a0 - a1*abs(_x[i] - 0.5) - a2*cos(2*PI*_x[i]); | |
break; | |
case 4: | |
// proposed by ralph beebe blackman (1904-1990). | |
// 'exact' blackman kills sidelobes 3 and 4, but only 6db/oct damping. | |
// 'unqualified' blackman still has the sidelobes, but an 18db/oct damping. | |
case "blackman": | |
// 'exact' blackman: | |
var a0 = 7938/18608; | |
var a1 = 9240/18608; | |
var a2 = 1430/18608; | |
// 'unqualified' blackman: | |
// var a0 = 0.42; | |
// var a1 = 0.5; | |
// var a2 = 0.08; | |
for(i in _x) _y[i] = a0 - a1 * cos(2.*PI*_x[i]) + a2 * cos(4.*PI*_x[i]); | |
break; | |
// generalized (variable center) blackman. | |
case "generalizedblackman": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
var _a = _args[0]; | |
var a0 = (1.0 - _a)/2.0; | |
var a1 = 0.5; | |
var a2 = _a / 2.0; | |
for(i in _x) | |
{ | |
var pix = PI*_x[i]; | |
_y[i] = a0 - a1*cos(2*pix) + a2*cos(4*pix); | |
} | |
break; | |
// blackman window improved by fred harris (brooklyn poly '61): | |
// http://web.mit.edu/xiphmont/Public/windows.pdf | |
// 4-term sinc functions to minimize sidelobes. | |
case 5: | |
case "blackman-harris": | |
var a0 = 0.35875; | |
var a1 = 0.48829; | |
var a2 = 0.14128; | |
var a3 = 0.01168; | |
for(i in _x) _y[i] = a0 - a1 * cos(2.*PI*_x[i]) + a2 * cos(4.*PI*_x[i]) + a3 * cos(6.*PI*_x[i]); | |
break; | |
// 4-term blackman-nuttal (same as BH with different coefficients). | |
case "blackman-nuttal": | |
var a0 = 0.3635819; | |
var a1 = 0.4891775; | |
var a2 = 0.1365995; | |
var a3 = 0.0106411; | |
for(i in _x) _y[i] = a0 - a1 * cos(2.*PI*_x[i]) + a2 * cos(4.*PI*_x[i]) + a3 * cos(6.*PI*_x[i]); | |
break; | |
// 4-term nuttal (same as BH with different coefficients). | |
case "nuttal": | |
var a0 = 0.355768; | |
var a1 = 0.487396; | |
var a2 = 0.144232; | |
var a3 = 0.012604; | |
for(i in _x) _y[i] = a0 - a1 * cos(2.*PI*_x[i]) + a2 * cos(4.*PI*_x[i]) + a3 * cos(6.*PI*_x[i]); | |
break; | |
// gaussians are eigenfunctions of fourier transforms. | |
// gaussian window needs to be zeroed at the ends. | |
// parabolic. | |
case 6: | |
case "gaussian": | |
if(!_args[0]) _args[0] = 0.4; // default sigma | |
var sigma = _args[0]; | |
for(i in _x) _y[i] = exp(-0.5 * ((_x[i]-0.5) / (sigma*0.5)) * ((_x[i]-0.5) / (sigma*0.5))); | |
break; | |
// jim kaiser's 1980 approximation of a DPSS / | |
// slepian window using bessel functions, | |
// developed at bell labs for audio coding. | |
// concentrates the energy in the main lobe. | |
case 7: | |
case "kaiser": | |
var alpha = 3.; | |
for(i in _x) { | |
var above = PI * alpha * sqrt(1.0 - (2. * _x[i] - 1.0) * (2. * _x[i] - 1.0)); | |
var below = PI * alpha; | |
_y[i] = besselI0(above) / besselI0(below); | |
} | |
break; | |
// an 'unwindow'. all samples within window envelope are at unity. | |
// bad signal-to-noise ratio. lots of scalloping loss. | |
// sometimes named for peter gustav lejeune dirichlet (1805-1859). | |
case 8: | |
case "rectangle": | |
case "boxcar": | |
case "dirichlet": | |
for(i in _x) _y[i] = 1.; // nothing to it | |
break; | |
// cosine window | |
case "cosine": | |
for(i in _x) _y[i] = sin(PI*_x[i]); | |
break; | |
// named for cornelius lanczos (1906-1974). | |
// a normalized (double-window) sinc function is often | |
// used as a kernel for interpolation / low-pass filtering. | |
case 9: | |
case "sinc": | |
case "sync": // learn to spell | |
case "lanczos": | |
for(i in _x) _y[i] = sinc(2*_x[i]-1.0); | |
break; | |
// flat top window. | |
case "flattop": | |
var a0 = 1.000; | |
var a1 = 1.930; | |
var a2 = 1.290; | |
var a3 = 0.388; | |
var a4 = 0.032; | |
for(i in _x) { | |
_y[i] = a0 - a1*cos(2*PI*_x[i]) + a2*cos(4*PI*_x[i]) - a3*cos(6*PI*_x[i]) + a4*cos(8*PI*_x[i]); | |
_y[i] /= (a0 + a1 + a2 + a3 + a4); | |
} | |
break; | |
// tukey window courtesy of @golanlevin : | |
// The Tukey window, also known as the tapered cosine window, | |
// can be regarded as a cosine lobe of width \tfrac{\alpha N}{2} | |
// that is convolved with a rectangle window of width \left(1 -\tfrac{\alpha}{2}\right)N. | |
// At alpha=0 it becomes rectangular, and at alpha=1 it becomes a Hann window. | |
case "tukey": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
var _a = _args[0]; | |
var ah = _a/2.0; | |
var omah = 1.0 - ah; | |
for(i in _x) | |
{ | |
_y[i] = 1.0; | |
if (_x[i] <= ah) { | |
_y[i] = 0.5 * (1.0 + cos(PI*((2*_x[i]/_a)-1.0))); | |
} | |
else if (_x[i] > omah) { | |
_y[i] = 0.5 * (1.0 + cos(PI*((2*_x[i]/_a)-(2/_a)+1.0))); | |
} | |
} | |
break; | |
// adjustable sliding gaussian courtesy of @golanlevin . | |
case "slidinggaussian": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
if(!_args[1]) _args[1] = 0.4; // default sigma | |
var dx = 2.0*(_args[0] - 0.5); | |
var sigma = _args[1] * 2.; | |
for(var i in _x) _y[i] = exp(0.0 - (sq(_x[i]*2.-1.0-dx) / (2.0*sigma*sigma))); | |
break; | |
// adjustable center cosine window courtesy of @golanlevin . | |
case "adjustablecosine": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
var _a = _args[0]; | |
var ah = _a/2.0; | |
var omah = 1.0 - ah; | |
for(i in _x) | |
{ | |
_y[i] = 1.0; | |
if (_x[i] <= _a) { | |
_y[i] = 0.5 * (1.0 + cos(PI*((_x[i]/_a)-1.0))); | |
} | |
else { | |
_y[i] = 0.5 * (1.0 + cos(PI*(((_x[i]-_a)/(1.0-_a))))); | |
} | |
} | |
break; | |
// adjustable center elliptic window courtesy of @golanlevin . | |
case "elliptic": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
var _a = _args[0]; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
for(i in _x) | |
{ | |
_y[i] = 0; | |
if (_x[i]<=_a){ | |
_y[i] = (1.0/_a) * sqrt(sq(_a) - sq(_x[i]-_a)); | |
} | |
else { | |
_y[i] = (1.0/(1-_a)) * sqrt(sq(1.0-_a) - sq(_x[i]-_a)); | |
} | |
} | |
break; | |
// adjustable center hyperelliptic window courtesy of @golanlevin . | |
case "hyperelliptic": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
if(!_args[1]) _args[1] = 3; // default order | |
var _a = _args[0]; | |
var _n = _args[1]; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
for(i in _x) | |
{ | |
_y[i] = 0; | |
var pwn = _n * 2.0; | |
if (_x[i]<=_a){ | |
_y[i] = (1.0/_a) * pow( pow(_a, pwn) - pow(_x[i]-_a, pwn), 1.0/pwn); | |
} | |
else { | |
_y[i] = ((1.0/ (1-_a))) * pow( pow(1.0-_a, pwn) - pow(_x[i]-_a, pwn), 1.0/pwn); | |
} | |
} | |
break; | |
// adjustable center squircular window courtesy of @golanlevin . | |
case "squircular": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
if(!_args[1]) _args[1] = 3; // default order | |
var _a = _args[0]; | |
var _n = _args[1]; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
for(i in _x) | |
{ | |
_y[i] = 0; | |
var pwn = max(2, _n * 2.0); | |
if (_x[i]<=_a){ | |
_y[i] = (1-_a) + pow( pow(_a, pwn) - pow(_x[i]-_a, pwn), 1.0/pwn); | |
} | |
else { | |
_y[i] = _a + pow( pow(1.0-_a, pwn) - pow(_x[i]-_a, pwn), 1.0/pwn); | |
} | |
} | |
break; | |
// poisson window functions courtesy of @golanlevin . | |
case "poisson": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
var tau = max(_args[0], Number.EPSILON); | |
for(var i in _x) _y[i] = exp (0.0 - (abs(_x[i] - 0.5))*(1.0/tau)); | |
break; | |
case "hann-poisson": | |
case "poisson-hann": | |
case "hannpoisson": | |
case "poissonhann": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
var tau = 25.0 * max(_args[0]*_args[0]*_args[0]*_args[0], Number.EPSILON); // nice control | |
for(i in _x) { | |
var hy = 0.5 * (1.0 - cos(TWO_PI*_x[i])); | |
var py = exp (0.0 - (abs(_x[i] - 0.5))*(1.0/tau)); | |
_y[i] = hy * py; | |
} | |
break; | |
case "slidinghann-poisson": | |
case "slidingpoisson-hann": | |
case "slidinghannpoisson": | |
case "slidingpoissonhann": | |
if(!_args[0]) _args[0] = 0.5; // default center | |
if(!_args[1]) _args[1] = 0.5; // default sigma | |
var tau = 25.0 * max(_args[1]*_args[1]*_args[1]*_args[1], Number.EPSILON); // nice control | |
for(i in _x) { | |
var newx = constrain(_x[i] + (0.5 - _args[0]), 0, 1); | |
var hy = 0.5 * (1.0 - cos(TWO_PI*newx)); | |
var py = exp (0.0 - (abs(newx - 0.5))*(1.0/tau)); | |
_y[i] = hy * py; | |
} | |
break; | |
for(i in _x) _y[i] = _x[i]; | |
default: | |
} | |
return(u ? _y : _y[0]); | |
} | |
// common waveform functions (0-1 evaluation). | |
p5.Gen.prototype.waveform = function(_x, _type) { | |
// algorithms: | |
// sine | |
// cosine | |
// saw / sawup | |
// sawdown | |
// phasor (ramp 0.-1.) | |
// square | |
// rect / rectangle | |
// pulse | |
// tri / triangle | |
// buzz | |
var u = true; // single value? | |
if(!Array.isArray(_x) && _x.constructor !== Float32Array && _x.constructor !== Float64Array) { | |
_x = [_x]; // process all values as arrays | |
u = false; | |
} | |
var _y // match type: | |
if(Array.isArray(_x)) _y = new Array(_x.length); | |
else if(_x.constructor === Float32Array) _y = new Float32Array(_x.length); | |
else if(_x.constructor === Float64Array) _y = new Float64Array(_x.length); | |
var i; | |
switch(_type) { | |
// sine wave 0. to 1. to -1. to 0. | |
case "sine": | |
case "sin": | |
_y = this.harmonics(_x, [1.]); | |
break; | |
// cosine wave 1. to -1. to 1. | |
case "cosine": | |
case "cos": | |
_y = this.triples(_x, [1., 1., 90]); | |
break; | |
// rising saw -1. to 1. | |
case "saw": | |
case "sawtooth": | |
case "sawup": | |
_y = this.bpf(_x, [0, -1., 1, 1.]); | |
break; | |
// falling saw 1. to -1. | |
case "sawdown": | |
_y = this.bpf(_x, [0, 1., 1, -1.]); | |
break; | |
// phasor ramp 0. to 1. | |
case "phasor": | |
_y = this.bpf(_x, [0, 0., 1, 1.]); | |
break; | |
// square wave 1. to -1. (equal duty cycle) | |
case "square": | |
_y = this.bpf(_x, [0, 1., 1, 1., 1, -1., 2, -1]); | |
break; | |
// rectangle wave 1. to -1. (10% duty cycle) | |
case "rect": | |
case "rectangle": | |
_y = this.bpf(_x, [0, 1., 1, 1., 1, -1., 10, -1]); | |
break; | |
// pulse wave 1. to -1. (1% duty cycle) | |
case "pulse": | |
_y = this.bpf(_x, [0, 1., 1, 1., 1, -1., 100, -1]); | |
break; | |
// triangle wave 0. to 1. to -1. to 0. | |
case "tri": | |
case "triangle": | |
_y = this.bpf(_x, [0, 0, 1, 1, 2, 0, 3, -1, 4, 0]); | |
break; | |
// buzz wave (10 harmonics at equal amplitude) 0. to 1. to -1. to 0. | |
case "buzz": | |
_y = this.harmonics(_x, [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1]); | |
break; | |
default: | |
} | |
return(u ? _y : _y[0]); | |
} | |
// list algorithms | |
p5.Gen.prototype.listAlgos = function() { | |
var _styles = new Array(); | |
for(var i in this.__proto__) { | |
var _t = true; | |
if(i=="listAlgos") _t=false; | |
else if(i=="fillArray") _t=false; | |
else if(i=="fillFloat32Array") _t=false; | |
else if(i=="fillFloat64Array") _t=false; | |
if(_t) _styles.push(i); | |
} | |
return(_styles); | |
} | |
// array xfer (for pre-rendered use) | |
p5.Gen.prototype.fillArray = function(_algo, _len, _args, _seed) { | |
var _p = new Array(_len); | |
var _dest = new Array(_len); | |
if(_algo==='random') { // 4th argument is seed | |
for(var i = 0;i<_len;i++) | |
{ | |
if(_seed) _p[i] = i/(_len-1)*10000.+_seed; | |
else _p[i] = '-1'; | |
} | |
} | |
else { | |
for(var i = 0;i<_len;i++) | |
{ | |
_p[i] = i/(_len-1); // 0.-1. | |
} | |
} | |
_dest = this[_algo](_p, _args, _seed); | |
return(_dest); | |
} | |
// array xfer (for pre-rendered use) | |
p5.Gen.prototype.fillFloat32Array = function(_algo, _len, _args, _seed) { | |
var _p = new Float32Array(_len); | |
var _dest = new Float32Array(_len); | |
if(_algo==='random') { // 4th argument is seed | |
for(var i = 0;i<_len;i++) | |
{ | |
if(_seed) _p[i] = i/(_len-1)*10000.+_seed; | |
else _p[i] = '-1'; | |
} | |
} | |
else { | |
for(var i = 0;i<_len;i++) | |
{ | |
_p[i] = i/(_len-1); // 0.-1. | |
} | |
} | |
_dest = this[_algo](_p, _args, _seed); | |
return(_dest); | |
} | |
// array xfer (for pre-rendered use) | |
p5.Gen.prototype.fillFloat64Array = function(_algo, _len, _args, _seed) { | |
var _p = new Float64Array(_len); | |
var _dest = new Float64Array(_len); | |
if(_algo==='random') { // 4th argument is seed | |
for(var i = 0;i<_len;i++) | |
{ | |
if(_seed) _p[i] = i/(_len-1)*10000.+_seed; | |
else _p[i] = '-1'; | |
} | |
} | |
else { | |
for(var i = 0;i<_len;i++) | |
{ | |
_p[i] = i/(_len-1); // 0.-1. | |
} | |
} | |
_dest = this[_algo](_p, _args, _seed); | |
return(_dest); | |
} | |
// ============================================================================= | |
// p5.Ease | |
// ============================================================================= | |
/** | |
* Base class for an easing function | |
* | |
* @class p5.Ease | |
* @constructor | |
*/ | |
p5.Ease = function() { | |
// | |
// this object generates easing / tweening functions | |
// through direct (0.-1.) evaluation, with utilities | |
// to pre-evaluate functions into lookup tables. | |
// | |
// algorithms based on: | |
// | |
// robert penner's algorithms discussed in | |
// 'programming macromedia flash mx' (2002). | |
// Copyright (C) 2001 Robert Penner, released under the BSD License | |
// | |
// golan levin's Pattern_Master functions: | |
// https://github.com/golanlevin/Pattern_Master | |
// Copyright (C) 2006 Golan Levin | |
// | |
// some functions have additional parameters, | |
// such as an order of interpolation (n) or up to four | |
// coefficients (a, b, c, d) which will change the | |
// behavior of the easing function. | |
// | |
this.version = 0.01; // just some crap for constructor | |
var that = this; // some bullshit | |
}; // end p5.Ease constructor | |
// Penner's Easing Functions: | |
// line y = x | |
p5.Ease.prototype.linear = function(_x) { | |
return(_x); | |
}; | |
// parabola y = x^2 | |
p5.Ease.prototype.quadraticIn = function(_x) { | |
return(_x * _x); | |
}; | |
// parabola y = -x^2 + 2x | |
p5.Ease.prototype.quadraticOut = function(_x) { | |
return(-(_x * (_x - 2))); | |
} | |
// piecewise quadratic | |
// y = (1/2)((2x)^2) ; [0, 0.5) | |
// y = -(1/2)((2x-1)*(2x-3) - 1) ; [0.5, 1] | |
p5.Ease.prototype.quadraticInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(2 * _x * _x); | |
} | |
else | |
{ | |
return((-2 * _x * _x) + (4 * _x) - 1); | |
} | |
} | |
// cubic y = x^3 | |
p5.Ease.prototype.cubicIn = function(_x) { | |
return(_x * _x * _x); | |
} | |
// cubic y = (x - 1)^3 + 1 | |
p5.Ease.prototype.cubicOut = function(_x) { | |
var _v = (_x - 1); | |
return(_v * _v * _v + 1); | |
} | |
// piecewise cubic | |
// y = (1/2)((2x)^3) ; [0, 0.5) | |
// y = (1/2)((2x-2)^3 + 2) ; [0.5, 1] | |
p5.Ease.prototype.cubicInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(4 * _x * _x * _x); | |
} | |
else | |
{ | |
var _v = ((2 * _x) - 2); | |
return(0.5 * _v * _v * _v + 1); | |
} | |
} | |
// quartic x^4 | |
p5.Ease.prototype.quarticIn = function(_x) { | |
return(_x * _x * _x * _x); | |
} | |
// quartic y = 1 - (x - 1)^4 | |
p5.Ease.prototype.quarticOut = function(_x) { | |
var _v = (_x - 1); | |
return(_v * _v * _v * (1 - _x) + 1); | |
} | |
// piecewise quartic | |
// y = (1/2)((2x)^4) ; [0, 0.5) | |
// y = -(1/2)((2x-2)^4 - 2) ; [0.5, 1] | |
p5.Ease.prototype.quarticInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(8 * _x * _x * _x * _x); | |
} | |
else | |
{ | |
var _v = (_x - 1); | |
return(-8 * _v * _v * _v * _v + 1); | |
} | |
} | |
// quintic y = x^5 | |
p5.Ease.prototype.quinticIn = function(_x) { | |
return(_x * _x * _x * _x * _x); | |
} | |
// quintic y = (x - 1)^5 + 1 | |
p5.Ease.prototype.quinticOut = function(_x) { | |
var _v = (_x - 1); | |
return(_v * _v * _v * _v * _v + 1); | |
} | |
// piecewise quintic | |
// y = (1/2)((2x)^5) ; [0, 0.5) | |
// y = (1/2)((2x-2)^5 + 2) ; [0.5, 1] | |
p5.Ease.prototype.quinticInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(16 * _x * _x * _x * _x * _x); | |
} | |
else | |
{ | |
var _v = ((2 * _x) - 2); | |
return(0.5 * _v * _v * _v * _v * _v + 1); | |
} | |
} | |
// quarter-cycle sine | |
p5.Ease.prototype.sineIn = function(_x) { | |
return(sin((_x - 1) * HALF_PI) + 1); | |
} | |
// quarter-cycle cosine | |
p5.Ease.prototype.sineOut = function(_x) { | |
return(sin(_x * HALF_PI)); | |
} | |
// half sine | |
p5.Ease.prototype.sineInOut = function(_x) { | |
return(0.5 * (1 - cos(_x * PI))); | |
} | |
// shifted quadrant IV of unit circle | |
p5.Ease.prototype.circularIn = function(_x) { | |
return(1 - sqrt(1 - (_x * _x))); | |
} | |
// shifted quadrant II of unit circle | |
p5.Ease.prototype.circularOut = function(_x) { | |
return(sqrt((2 - _x) * _x)); | |
} | |
// piecewise circular function | |
// y = (1/2)(1 - sqrt(1 - 4x^2)) ; [0, 0.5) | |
// y = (1/2)(sqrt(-(2x - 3)*(2x - 1)) + 1) ; [0.5, 1] | |
p5.Ease.prototype.circularInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(0.5 * (1 - sqrt(1 - 4 * (_x * _x)))); | |
} | |
else | |
{ | |
return(0.5 * (sqrt(-((2 * _x) - 3) * ((2 * _x) - 1)) + 1)); | |
} | |
} | |
// exponential function y = 2^(10(x - 1)) | |
p5.Ease.prototype.exponentialIn = function(_x) { | |
return((_x == 0.0) ? _x : pow(2, 10 * (_x - 1))); | |
} | |
// exponential function y = -2^(-10x) + 1 | |
p5.Ease.prototype.exponentialOut = function(_x) { | |
return((_x == 1.0) ? _x : 1 - pow(2, -10 * _x)); | |
} | |
// piecewise exponential | |
// y = (1/2)2^(10(2x - 1)) ; [0,0.5) | |
// y = -(1/2)*2^(-10(2x - 1))) + 1 ; [0.5,1] | |
p5.Ease.prototype.exponentialInOut = function(_x) { | |
if(_x == 0.0 || _x == 1.0) return _x; | |
if(_x < 0.5) | |
{ | |
return(0.5 * pow(2, (20 * _x) - 10)); | |
} | |
else | |
{ | |
return(-0.5 * pow(2, (-20 * _x) + 10) + 1); | |
} | |
} | |
// damped sine wave y = sin(13pi/2*x)*pow(2, 10 * (x - 1)) | |
p5.Ease.prototype.elasticIn = function(_x) { | |
return(sin(13 * HALF_PI * _x) * pow(2, 10 * (_x - 1))); | |
} | |
// damped sine wave y = sin(-13pi/2*(x + 1))*pow(2, -10x) + 1 | |
p5.Ease.prototype.elasticOut = function(_x) { | |
return(sin(-13 * HALF_PI * (_x + 1)) * pow(2, -10 * _x) + 1); | |
} | |
// piecewise damped sine wave: | |
// y = (1/2)*sin(13pi/2*(2*x))*pow(2, 10 * ((2*x) - 1)) ; [0,0.5) | |
// y = (1/2)*(sin(-13pi/2*((2x-1)+1))*pow(2,-10(2*x-1)) + 2) ; [0.5, 1] | |
p5.Ease.prototype.elasticInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(0.5 * sin(13 * HALF_PI * (2 * _x)) * pow(2, 10 * ((2 * _x) - 1))); | |
} | |
else | |
{ | |
return(0.5 * (sin(-13 * HALF_PI * ((2 * _x - 1) + 1)) * pow(2, -10 * (2 * _x - 1)) + 2)); | |
} | |
} | |
// overshooting cubic y = x^3-x*sin(x*pi) | |
p5.Ease.prototype.backIn = function(_x) { | |
return(_x * _x * _x - _x * sin(_x * PI)); | |
} | |
// overshooting cubic y = 1-((1-x)^3-(1-x)*sin((1-x)*pi)) | |
p5.Ease.prototype.backOut = function(_x) { | |
var f = (1 - _x); | |
return(1 - (f * f * f - f * sin(f * PI))); | |
} | |
// piecewise overshooting cubic function: | |
// y = (1/2)*((2x)^3-(2x)*sin(2*x*pi)) ; [0, 0.5) | |
// y = (1/2)*(1-((1-x)^3-(1-x)*sin((1-x)*pi))+1) ; [0.5, 1] | |
p5.Ease.prototype.backInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
var f = 2 * _x; | |
return(0.5 * (f * f * f - f * sin(f * PI))); | |
} | |
else | |
{ | |
var f = (1 - (2*_x - 1)); | |
return(0.5 * (1 - (f * f * f - f * sin(f * PI))) + 0.5); | |
} | |
} | |
// penner's four-part bounce algorithm | |
p5.Ease.prototype.bounceIn = function(_x) { | |
return(1 - this.bounceOut(1 - _x)); | |
} | |
// penner's four-part bounce algorithm | |
p5.Ease.prototype.bounceOut = function(_x) { | |
if(_x < 4/11.0) | |
{ | |
return((121 * _x * _x)/16.0); | |
} | |
else if(_x < 8/11.0) | |
{ | |
return((363/40.0 * _x * _x) - (99/10.0 * _x) + 17/5.0); | |
} | |
else if(_x < 9/10.0) | |
{ | |
return((4356/361.0 * _x * _x) - (35442/1805.0 * _x) + 16061/1805.0); | |
} | |
else | |
{ | |
return((54/5.0 * _x * _x) - (513/25.0 * _x) + 268/25.0); | |
} | |
} | |
// piecewise penner's four-part bounce algorithm | |
p5.Ease.prototype.bounceInOut = function(_x) { | |
if(_x < 0.5) | |
{ | |
return(0.5 * this.bounceIn(_x*2)); | |
} | |
else | |
{ | |
return(0.5 * this.bounceOut(_x * 2 - 1) + 0.5); | |
} | |
} | |
// Golan's Pattern Master Functions: | |
// bryce summers' cubic easing function (@Bryce-Summers) | |
p5.Ease.prototype.brycesCubic = function(_x, _n) | |
{ | |
if(!_n) _n = 3; // default | |
var p = pow(_x, _n-1); | |
var xn = p * _x; | |
return(_n*p - (_n-1)*xn); | |
} | |
// staircase function - n is # of steps | |
p5.Ease.prototype.staircase = function(_x, _n) | |
{ | |
if(!_n) _n = 3; // default | |
var _y = floor(_x*_n) / (_n-1); | |
if(_x>=1.) _y=1.; | |
return(_y); | |
} | |
// staircase function with smoothing - a is smoothing, n is # of steps | |
p5.Ease.prototype.exponentialSmoothedStaircase = function(_x, _a, _n) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_n) _n = 3; // default | |
// See http://web.mit.edu/fnl/volume/204/winston.html | |
var fa = sq (map(_a, 0,1, 5,30)); | |
var _y = 0; | |
for (var i=0; i<_n; i++){ | |
_y += (1.0/(_n-1.0))/ (1.0 + exp(fa*(((i+1.0)/_n) - _x))); | |
} | |
_y = constrain(_y, 0,1); | |
return(_y); | |
} | |
// gompertz curve | |
// http://en.wikipedia.org/wiki/Gompertz_curve | |
p5.Ease.prototype.gompertz = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
_a = max(_a, min_param_a); | |
var b = -8.0; | |
var c = 0 - _a*16.0; | |
var _y = exp( b * exp(c * _x)); | |
var maxVal = exp(b * exp(c)); | |
var minVal = exp(b); | |
_y = map(_y, minVal, maxVal, 0, 1); | |
return(_y); | |
} | |
// clamped processing map function with terms reordered: | |
// min in, max in, min out, max out | |
p5.Ease.prototype.generalizedLinearMap = function(_x, _a, _b, _c, _d) | |
{ | |
if(!_a) _a = 0.; // default | |
if(!_b) _b = 0.; // default | |
if(!_c) _c = 1.; // default | |
if(!_d) _d = 1.; // default | |
var _y = 0; | |
if (_a < _c) { | |
if (_x <= _a) { | |
_y = _b; | |
} | |
else if (_x >= _c) { | |
_y = _d; | |
} | |
else { | |
_y = map(_x, _a, _c, _b, _d); | |
} | |
} | |
else { | |
if (_x <= _c) { | |
_y = _d; | |
} | |
else if (_x >= _a) { | |
_y = _b; | |
} | |
else { | |
_y = map(_x, _c, _a, _d, _b); | |
} | |
} | |
return(_y); | |
} | |
// double-(odd) polynomial ogee | |
// what the hell is an ogee, you may ask? | |
// https://en.wikipedia.org/wiki/Ogee | |
p5.Ease.prototype.doubleOddPolynomialOgee = function(_x, _a, _b, _n) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_n) _n = 3; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
var p = 2*_n + 1; | |
var _y = 0; | |
if (_x <= _a) { | |
_y = _b - _b*pow(1-_x/_a, p); | |
} | |
else { | |
_y = _b + (1-_b)*pow((_x-_a)/(1-_a), p); | |
} | |
return(_y); | |
} | |
// double-linear interpolator | |
p5.Ease.prototype.doubleLinear = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var _y = 0; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
if (_x<=_a) { | |
_y = (_b/_a) * _x; | |
} | |
else { | |
_y = _b + ((1-_b)/(1-_a))*(_x-_a); | |
} | |
return(_y); | |
} | |
// triple-linear interpolator | |
p5.Ease.prototype.tripleLinear = function(_x, _a, _b, _c, _d) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_c) _c = 0.75; // default | |
if(!_d) _d = 0.25; // default | |
var _y = 0; | |
if (_a < _c) { | |
if (_x <= _a) { | |
_y = map(_x, 0, _a, 0, _b); | |
} | |
else if (_x >= _c) { | |
_y = map(_x, _c, 1, _d, 1); | |
} | |
else { | |
_y = map(_x, _a, _c, _b, _d); | |
} | |
} | |
else { | |
if (_x <= _c) { | |
_y = map(_x, 0, _c, 0, _d); | |
} | |
else if (_x >= _a) { | |
_y = map(_x, _a, 1, _b, 1); | |
} | |
else { | |
_y = map(_x, _c, _a, _d, _b); | |
} | |
} | |
return(_y); | |
} | |
// variable staircase interpolator | |
p5.Ease.prototype.variableStaircase = function(_x, _a, _n) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_n) _n = 3; // default | |
var aa = (_a - 0.5); | |
if (aa == 0) { | |
return(_x); | |
} | |
var x0 = (floor (_x*_n))/ _n; | |
var x1 = (ceil (_x*_n))/ _n; | |
var y0 = x0; | |
var y1 = x1; | |
var px = 0.5*(x0+x1) + aa/_n; | |
var py = 0.5*(x0+x1) - aa/_n; | |
var _y = 0; | |
if ((_x < px) && (_x > x0)) { | |
_y = map(_x, x0, px, y0, py); | |
} | |
else { | |
_y = map(_x, px, x1, py, y1); | |
} | |
return(_y); | |
} | |
// quadratic bezier staircase function | |
p5.Ease.prototype.quadraticBezierStaircase = function(_x, _a, _n) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_n) _n = 3; // default | |
var aa = (_a - 0.5); | |
if (aa == 0) { | |
return(_x); | |
} | |
var x0 = (floor (_x*_n))/ _n; | |
var x1 = (ceil (_x*_n))/ _n; | |
var y0 = x0; | |
var y1 = x1; | |
var px = 0.5*(x0+x1) + aa/_n; | |
var py = 0.5*(x0+x1) - aa/_n; | |
var p0x = (x0 + px)/2.0; | |
var p0y = (y0 + py)/2.0; | |
var p1x = (x1 + px)/2.0; | |
var p1y = (y1 + py)/2.0; | |
var _y = 0; | |
var denom = (1.0/_n)*0.5; | |
if ((_x <= p0x) && (_x >= x0)) { | |
// left side | |
if (floor (_x*_n) <= 0){ | |
_y = map(_x, x0, px, y0, py); | |
} else { | |
if (abs(_x - x0) < Number.EPSILON){ | |
// problem when x == x0 ! | |
} | |
var za = (x0 - (p1x - 1.0/_n))/denom; | |
var zb = (y0 - (p1y - 1.0/_n))/denom; | |
var zx = (_x - (p1x - 1.0/_n))/denom; | |
var om2a = 1.0 - 2.0*za; | |
var interior = max (0, za*za + om2a*zx); | |
var t = (sqrt(interior) - za)/om2a; | |
var zy = (1.0-2.0*zb)*(t*t) + (2*zb)*t; | |
zy *= (p1y - p0y); | |
zy += p1y; //(p1y - 1.0/n); | |
if (_x > x0){ | |
zy -= 1.0/_n; | |
} | |
_y = zy; | |
} | |
} | |
else if ((_x >= p1x) && (_x <= x1)) { | |
// right side | |
if (ceil(_x*_n) >= _n) { | |
_y = map(_x, px, x1, py, y1); | |
} | |
else { | |
if (abs(_x - x1) < Number.EPSILON){ | |
// problem when x == x1 ! | |
} | |
var za = (x1 - p1x)/denom; | |
var zb = (y1 - p1y)/denom; | |
var zx = (_x - p1x)/denom; | |
if (za == 0.5) { | |
za += Number.EPSILON; | |
} | |
var om2a = 1.0 - 2.0*za; | |
if (abs(om2a) < Number.EPSILON) { | |
om2a = ((om2a < 0) ? -1:1) * Number.EPSILON; | |
} | |
var interior = max (0, za*za + om2a*zx); | |
var t = (sqrt(interior) - za)/om2a; | |
var zy = (1.0-2.0*zb)*(t*t) + (2*zb)*t; | |
zy *= (p1y - p0y); | |
zy += p1y; | |
_y = zy; | |
} | |
} | |
else { | |
// center | |
var za = (px - p0x)/denom; | |
var zb = (py - p0y)/denom; | |
var zx = (_x - p0x)/denom; | |
if (za == 0.5) { | |
za += Number.EPSILON; | |
} | |
var om2a = 1.0 - 2.0*za; | |
var t = (sqrt(za*za + om2a*zx) - za)/om2a; | |
var zy = (1.0-2.0*zb)*(t*t) + (2*zb)*t; | |
zy *= (p1y - p0y); | |
zy += p0y; | |
_y = zy; | |
} | |
return(_y); | |
} | |
// symmetric double-element sigmoid function (a is slope) | |
// https://en.wikipedia.org/wiki/Sigmoid_function | |
p5.Ease.prototype.doubleExponentialSigmoid = function(_x, _a) | |
{ | |
if(!_a) _a = 0.75; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_a = 1-_a; | |
var _y = 0; | |
if (_x<=0.5){ | |
_y = (pow(2.0*_x, 1.0/_a))/2.0; | |
} | |
else { | |
_y = 1.0 - (pow(2.0*(1.0-_x), 1.0/_a))/2.0; | |
} | |
return(_y); | |
} | |
// double-element sigmoid function with an adjustable center (b is center) | |
p5.Ease.prototype.adjustableCenterDoubleExponentialSigmoid = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.75; // default | |
if(!_b) _b = 0.5; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_a = 1-_a; | |
var _y = 0; | |
var w = max(0, min(1, _x-(_b-0.5))); | |
if (w<=0.5){ | |
_y = (pow(2.0*w, 1.0/_a))/2.0; | |
} | |
else { | |
_y = 1.0 - (pow(2.0*(1.0-w), 1.0/_a))/2.0; | |
} | |
return(_y); | |
} | |
// quadratic sigmoid function | |
p5.Ease.prototype.doubleQuadraticSigmoid = function(_x) | |
{ | |
var _y = 0; | |
if (_x<=0.5){ | |
_y = sq(2.0*_x)/2.0; | |
} | |
else { | |
_y = 1.0 - sq(2.0*(_x-1.0))/2.0; | |
} | |
return(_y); | |
} | |
// double polynomial sigmoid function | |
p5.Ease.prototype.doublePolynomialSigmoid = function(_x, _n) | |
{ | |
if(!_n) _n = 3; // default | |
var _y = 0; | |
if (_n%2 == 0){ | |
// even polynomial | |
if (_x<=0.5){ | |
_y = pow(2.0*_x, _n)/2.0; | |
} | |
else { | |
_y = 1.0 - pow(2*(_x-1.0), _n)/2.0; | |
} | |
} | |
else { | |
// odd polynomial | |
if (_x<=0.5){ | |
_y = pow(2.0*_x, _n)/2.0; | |
} | |
else { | |
_y = 1.0 + pow(2.0*(_x-1.0), _n)/2.0; | |
} | |
} | |
return(_y); | |
} | |
// double elliptic ogee | |
// http://www.flong.com/texts/code/shapers_circ/ | |
p5.Ease.prototype.doubleEllipticOgee = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
var _y = 0; | |
if (_x<=_a){ | |
_y = (_b/_a) * sqrt(sq(_a) - sq(_x-_a)); | |
} | |
else { | |
_y = 1.0 - ((1.0-_b)/(1.0-_a))*sqrt(sq(1.0-_a) - sq(_x-_a)); | |
} | |
return(_y); | |
} | |
// double-cubic ogee | |
p5.Ease.prototype.doubleCubicOgee = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
var _y = 0; | |
if (_x <= _a){ | |
_y = _b - _b*pow(1.0-_x/_a, 3.0); | |
} | |
else { | |
_y = _b + (1.0-_b)*pow((_x-_a)/(1.0-_a), 3.0); | |
} | |
return(_y); | |
} | |
// double circular sigmoid function | |
p5.Ease.prototype.doubleCircularSigmoid = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var _y = 0; | |
if (_x<=_a) { | |
_y = _a - sqrt(_a*_a - _x*_x); | |
} | |
else { | |
_y = _a + sqrt(sq(1.0-_a) - sq(_x-1.0)); | |
} | |
return(_y); | |
} | |
// double squircular sigmoid function | |
p5.Ease.prototype.doubleSquircularSigmoid = function(_x, _a, _n) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_n) _n = 3; // default | |
var pwn = max(2, _n * 2.0); | |
var _y = 0; | |
if (_x<=_a) { | |
_y = _a - pow( pow(_a,pwn) - pow(_x,pwn), 1.0/pwn); | |
} | |
else { | |
_y = _a + pow(pow(1.0-_a, pwn) - pow(_x-1.0, pwn), 1.0/pwn); | |
} | |
return(_y); | |
} | |
// double quadratic bezier curve | |
// http://engineeringtraining.tpub.com/14069/css/14069_150.htm | |
p5.Ease.prototype.doubleQuadraticBezier = function(_x, _a, _b, _c, _d) | |
{ | |
// produces mysterious values when a=0,b=1,c=0.667,d=0.417 | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_c) _c = 0.75; // default | |
if(!_d) _d = 0.25; // default | |
var xmid = (_a + _c)/2.0; | |
var ymid = (_b + _d)/2.0; | |
xmid = constrain (xmid, Number.EPSILON, 1.0-Number.EPSILON); | |
ymid = constrain (ymid, Number.EPSILON, 1.0-Number.EPSILON); | |
var _y = 0; | |
var om2a; | |
var t; | |
var xx; | |
var aa; | |
var bb; | |
if (_x <= xmid){ | |
xx = _x / xmid; | |
aa = _a / xmid; | |
bb = _b / ymid; | |
om2a = 1.0 - 2.0*aa; | |
if (om2a == 0) { | |
om2a = Number.EPSILON; | |
} | |
t = (sqrt(aa*aa + om2a*xx) - aa)/om2a; | |
_y = (1.0-2.0*bb)*(t*t) + (2*bb)*t; | |
_y *= ymid; | |
} | |
else { | |
xx = (_x - xmid)/(1.0-xmid); | |
aa = (_c - xmid)/(1.0-xmid); | |
bb = (_d - ymid)/(1.0-ymid); | |
om2a = 1.0 - 2.0*aa; | |
if (om2a == 0) { | |
om2a = Number.EPSILON; | |
} | |
t = (sqrt(aa*aa + om2a*xx) - aa)/om2a; | |
_y = (1.0-2.0*bb)*(t*t) + (2*bb)*t; | |
_y *= (1.0 - ymid); | |
_y += ymid; | |
} | |
return(_y); | |
} | |
// double-elliptic sigmoid function | |
p5.Ease.prototype.doubleEllipticSigmoid = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var _y = 0; | |
if (_x<=_a){ | |
if (_a <= 0){ | |
_y = 0; | |
} else { | |
_y = _b * (1.0 - (sqrt(sq(_a) - sq(_x))/_a)); | |
} | |
} | |
else { | |
if (_a >= 1){ | |
_y = 1.0; | |
} else { | |
_y = _b + ((1.0-_b)/(1.0-_a))*sqrt(sq(1.0-_a) - sq(_x-1.0)); | |
} | |
} | |
return(_y); | |
} | |
// simplified double-cubic ogee | |
p5.Ease.prototype.doubleCubicOgeeSimplified = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
_b = 1 - _b; //reverse, for intelligibility. | |
var _y = 0; | |
if (_x<=_a){ | |
if (_a <= 0){ | |
_y = 0; | |
} else { | |
var val = 1 - _x/_a; | |
_y = _b*_x + (1-_b)*_a*(1.0- val*val*val); | |
} | |
} | |
else { | |
if (_a >= 1){ | |
_y = 1; | |
} else { | |
var val = (_x-_a)/(1-_a); | |
_y = _b*_x + (1-_b)*(_a + (1-_a)* val*val*val); | |
} | |
} | |
return(_y); | |
} | |
// raised inverted cosine function | |
p5.Ease.prototype.raisedInvertedCosine = function(_x) | |
{ | |
var _y = (1.0 - cos(PI*_x))/2.0; | |
return(_y); | |
} | |
// blinn / wyvill's cosine approximation | |
// http://www.flong.com/texts/code/shapers_poly/ | |
p5.Ease.prototype.cosineApproximation = function(_x) | |
{ | |
var x2 = _x*_x; | |
var x4 = x2*x2; | |
var x6 = x4*x2; | |
var fa = (4.0/9.0); | |
var fb = (17.0/9.0); | |
var fc = (22.0/9.0); | |
var _y = fa*x6 - fb*x4 + fc*x2; | |
return(_y); | |
} | |
// smoothstep function | |
// https://en.wikipedia.org/wiki/Smoothstep | |
p5.Ease.prototype.smoothStep = function(_x) | |
{ | |
return(_x*_x*(3.0 - 2.0*_x)); | |
} | |
// ken perlin's 'smoother step' smoothstep function | |
// https://www.amazon.com/Texturing-Modeling-Third-Procedural-Approach/dp/1558608486 | |
p5.Ease.prototype.smootherStep = function(_x) | |
{ | |
return(_x*_x*_x*(_x*(_x*6.0 - 15.0) + 10.0)); | |
} | |
// maclaurin cosine approximation | |
// http://blogs.ubc.ca/infiniteseriesmodule/units/unit-3-power-series/taylor-series/the-maclaurin-expansion-of-cosx/ | |
p5.Ease.prototype.maclaurinCosine = function(_x) | |
{ | |
var nTerms = 6; // anything less is fouled | |
_x *= PI; | |
var xp = 1.0; | |
var x2 = _x*_x; | |
var sig = 1.0; | |
var fact = 1.0; | |
var _y = xp; | |
for (var i=0; i<nTerms; i++) { | |
xp *= x2; | |
sig = 0-sig; | |
fact *= (i*2+1); | |
fact *= (i*2+2); | |
_y += sig * (xp / fact); | |
} | |
_y = (1.0 - _y)/2.0; | |
return(_y); | |
} | |
// paul bourke's catmull rom spline | |
// https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline | |
// from http://paulbourke.net/miscellaneous/interpolation/ | |
p5.Ease.prototype.catmullRomInterpolate = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var y0 = _a; | |
var y3 = _b; | |
var x2 = _x*_x; | |
var a0 = -0.5*y0 + 0.5*y3 - 1.5 ; | |
var a1 = y0 - 0.5*y3 + 2.0 ; | |
var a2 = -0.5*y0 + 0.5 ; | |
var _y = a0*_x*x2 + a1*x2 + a2*_x; | |
return(constrain(_y, 0, 1)); | |
} | |
// hermite polynomial function | |
// https://en.wikipedia.org/wiki/Hermite_polynomials | |
// from http://musicdsp.org/showArchiveComment.php?ArchiveID=93 | |
// by Laurent de Soras | |
p5.Ease.prototype.hermite = function(_x, _a, _b, _c, _d) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.; // default - ??? | |
if(!_c) _c = 1.; // default - ??? | |
if(!_d) _d = 0.25; // default | |
_a = map(_a, 0,1, -1,1); | |
_c = map(_c, 0,1, -1,1); | |
var hC = (_c - _a) * 0.5; | |
var hV = (_b - _c); | |
var hW = hC + hV; | |
var hA = hW + hV + (_d - _b) * 0.5; | |
var hB = hW + hA; | |
var _y = (((hA * _x) - hB) * _x + hC) * _x + _b; | |
return(_y); | |
} | |
// hermite polynomial function | |
// from http://paulbourke.net/miscellaneous/interpolation/ | |
p5.Ease.prototype.hermite2 = function(_x, _a, _b, _c, _d) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_c) _c = 0.75; // default | |
if(!_d) _d = 0.25; // default | |
// | |
// Tension: 1 is high, 0 normal, -1 is low | |
// Bias: 0 is even, positive is towards first segment, negative towards the other | |
// | |
var tension = map (_c, 0,1, -1,1); | |
var bias = map (_d, 0,1, -1,1); | |
var y0 = 2.0 * (_a - 0.5); //? a | |
var y1 = 0.0; | |
var y2 = 1.0; | |
var y3 = _b; | |
var x2 = _x * _x; | |
var x3 = x2 * _x; | |
var m0, m1; | |
m0 = (y1-y0)*(1.0+bias)*(1.0-tension)/2.0; | |
m0 += (y2-y1)*(1.0-bias)*(1.0-tension)/2.0; | |
m1 = (y2-y1)*(1.0+bias)*(1.0-tension)/2.0; | |
m1 += (y3-y2)*(1.0-bias)*(1.0-tension)/2.0; | |
var a0 = 2.0*x3 - 3.0*x2 + 1.0; | |
var a1 = x3 - 2.0*x2 + _x; | |
var a2 = x3 - x2; | |
var a3 = -2.0*x3 + 3.0*x2; | |
var _y = a0*y1 + a1*m0 + a2*m1 + a3*y2; | |
return(_y); | |
} | |
// error function | |
// http://en.wikipedia.org/wiki/Error_function | |
// Note that this implementation is a shifted, scaled and normalized error function! | |
p5.Ease.prototype.normalizedErf = function(_x) | |
{ | |
var erfBound = 2.0; // set bounds for artificial "normalization" | |
var erfBoundNorm = 0.99532226501; // this = erf(2.0), i.e., erf(erfBound) | |
var z = map(_x, 0.0, 1.0, 0-erfBound, erfBound); | |
var z2 = z*z; | |
var a = (8.0*(PI-3.0)) / ((3*PI)*(4.0-PI)); | |
var _y = sqrt (1.0 - exp(0 - z2*( (a*z2 + 4.0/PI) / (a*z2 + 1.0)))); | |
if (z < 0.0) _y = 0-_y; | |
_y /= erfBoundNorm; | |
_y = (_y+1.0) / 2.0; | |
return(_y); | |
} | |
// inverse error function | |
p5.Ease.prototype.normalizedInverseErf = function(_x) | |
{ | |
var erfBound = 2.0; | |
var erfBoundNorm = 0.99532226501; // this = erf(2.0), i.e., erf(erfBound) | |
var z = map(_x, 0, 1, -erfBoundNorm, erfBoundNorm); | |
var z2 = z*z; | |
var a = (8.0*(PI-3.0)) / ((3*PI)*(4.0-PI)); | |
var A = (2.0 / (PI *a)) + (log(1.0-z2) / 2.0); | |
var B = (log(1.0-z2) / a); | |
var _y = sqrt( sqrt(A*A - B) - A ); | |
if (z < 0.0) _y = 0-_y; | |
_y /= erfBound; | |
_y = (_y+1.0); | |
_y /= 2.0; | |
_y = constrain(_y, 0, 1); // necessary | |
return(_y); | |
} | |
// exponential emphasis function | |
p5.Ease.prototype.exponentialEmphasis = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
if (_a < 0.5) { | |
// emphasis | |
_a = 2*(_a); | |
var _y = pow(_x, _a); | |
return(_y); | |
} | |
else { | |
// de-emphasis | |
_a = 2*(_a-0.5); | |
var _y = pow(_x, 1.0/(1-_a)); | |
return(_y); | |
} | |
} | |
// iterative square root | |
// http://en.wikipedia.org/wiki/Methods_of_computing_square_roots | |
// ancient babylonian technology | |
p5.Ease.prototype.iterativeSquareRoot = function(_x) | |
{ | |
var _y = 0.5; | |
var n = 6; | |
for (var i=0; i<n; i++) { | |
_y = (_y + _x/_y)/2.0; | |
} | |
return(_y); | |
} | |
// fast inverse square root | |
// http://en.wikipedia.org/wiki/Fast_inverse_square_root | |
// http://stackoverflow.com/questions/11513344/how-to-implement-the-fast-inverse-square-root-in-java | |
p5.Ease.prototype.fastSquareRoot = function(_x) | |
{ | |
var xhalf = 0.5 * _x; | |
var i = f2ib(_x); | |
i = 0x5f3759df - (i>>1); | |
_x = ib2f(i); | |
_x = _x*(1.5 - xhalf*_x*_x); | |
return(1.0/_x); | |
} | |
// symmetric double-exponential ogee | |
p5.Ease.prototype.doubleExponentialOgee = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
var _y = 0; | |
if (_x<=0.5){ | |
_y = (pow(2.0*_x, 1.0-_a))/2.0; | |
} | |
else { | |
_y = 1.0 - (pow(2.0*(1.0-_x), 1.0-_a))/2.0; | |
} | |
return(_y); | |
} | |
// joining two lines with a circular arc fillet | |
// Adapted from robert d. miller / graphics gems | |
// http://www.realtimerendering.com/resources/GraphicsGems/gemsiii/fillet.c | |
p5.Ease.prototype.circularFillet = function(_x, _a, _b, _c) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_c) _c = 0.75; // default | |
var arcStartAngle = 0; | |
var arcEndAngle = 0; | |
var arcStartX = 0; | |
var arcStartY = 0; | |
var arcEndX = 0; | |
var arcEndY = 0; | |
var arcCenterX = 0; | |
var arcCenterY = 0; | |
var arcRadius = 0; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0 + Number.EPSILON; | |
var max_param_b = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
var R = _c; | |
// helper function: | |
// return signed distance from line Ax + By + C = 0 to point P. | |
function linetopoint(a, b, c, ptx, pty) { | |
var lp = 0.0; | |
var d = sqrt((a*a)+(b*b)); | |
if (d != 0.0) { | |
lp = (a*ptx + b*pty + c)/d; | |
} | |
return(lp); | |
} | |
// compute fillet parameters: | |
computefillet: { | |
var p1x = 0; | |
var p1y = 0; | |
var p2x = _a; | |
var p2y = _b; | |
var p3x = _a; | |
var p3y = _b; | |
var p4x = 1; | |
var p4y = 1; | |
var r = R; | |
var c1 = p2x*p1y - p1x*p2y; | |
var a1 = p2y-p1y; | |
var b1 = p1x-p2x; | |
var c2 = p4x*p3y - p3x*p4y; | |
var a2 = p4y-p3y; | |
var b2 = p3x-p4x; | |
if ((a1*b2) == (a2*b1)) { /* Parallel or coincident lines */ | |
break computefillet; | |
} | |
var d1, d2; | |
var mPx, mPy; | |
mPx= (p3x + p4x)/2.0; | |
mPy= (p3y + p4y)/2.0; | |
d1 = linetopoint(a1, b1, c1, mPx, mPy); /* Find distance p1p2 to p3 */ | |
if (d1 == 0.0) { | |
break computefillet; | |
} | |
mPx= (p1x + p2x)/2.0; | |
mPy= (p1y + p2y)/2.0; | |
d2 = linetopoint(a2, b2, c2, mPx, mPy); /* Find distance p3p4 to p2 */ | |
if (d2 == 0.0) { | |
break computefillet; | |
} | |
var c1p, c2p, d; | |
var rr = r; | |
if (d1 <= 0.0) { | |
rr= -rr; | |
} | |
c1p = c1 - rr*sqrt((a1*a1)+(b1*b1)); /* Line parallel l1 at d */ | |
rr = r; | |
if (d2 <= 0.0) { | |
rr = -rr; | |
} | |
c2p = c2 - rr*sqrt((a2*a2)+(b2*b2)); /* Line parallel l2 at d */ | |
d = (a1*b2)-(a2*b1); | |
var pCx = (c2p*b1-c1p*b2)/d; /* Intersect constructed lines */ | |
var pCy = (c1p*a2-c2p*a1)/d; /* to find center of arc */ | |
var pAx = 0; | |
var pAy = 0; | |
var pBx = 0; | |
var pBy = 0; | |
var dP, cP; | |
dP = (a1*a1) + (b1*b1); /* Clip or extend lines as required */ | |
if (dP != 0.0) { | |
cP = a1*pCy - b1*pCx; | |
pAx = (-a1*c1 - b1*cP)/dP; | |
pAy = ( a1*cP - b1*c1)/dP; | |
} | |
dP = (a2*a2) + (b2*b2); | |
if (dP != 0.0) { | |
cP = a2*pCy - b2*pCx; | |
pBx = (-a2*c2 - b2*cP)/dP; | |
pBy = ( a2*cP - b2*c2)/dP; | |
} | |
var gv1x = pAx-pCx; | |
var gv1y = pAy-pCy; | |
var gv2x = pBx-pCx; | |
var gv2y = pBy-pCy; | |
var arcStart = atan2(gv1y, gv1x); | |
var arcAngle = 0.0; | |
var dd = sqrt(((gv1x*gv1x)+(gv1y*gv1y)) * ((gv2x*gv2x)+(gv2y*gv2y))); | |
if (dd != 0.0) { | |
arcAngle = (acos((gv1x*gv2x + gv1y*gv2y)/dd)); | |
} | |
var crossProduct = (gv1x*gv2y - gv2x*gv1y); | |
if (crossProduct < 0.0) { | |
arcStart -= arcAngle; | |
} | |
var arc1 = arcStart; | |
var arc2 = arcStart + arcAngle; | |
if (crossProduct < 0.0) { | |
arc1 = arcStart + arcAngle; | |
arc2 = arcStart; | |
} | |
arcCenterX = pCx; | |
arcCenterY = pCy; | |
arcStartAngle = arc1; | |
arcEndAngle = arc2; | |
arcRadius = r; | |
arcStartX = arcCenterX + arcRadius*cos(arcStartAngle); | |
arcStartY = arcCenterY + arcRadius*sin(arcStartAngle); | |
arcEndX = arcCenterX + arcRadius*cos(arcEndAngle); | |
arcEndY = arcCenterY + arcRadius*sin(arcEndAngle); | |
} | |
// end compute | |
var t = 0; | |
var y = 0; | |
_x = constrain(_x, 0, 1); | |
if (_x <= arcStartX) { | |
if (arcStartX < Math.EPSILON){ | |
_y = 0; | |
} else { | |
t = _x / arcStartX; | |
_y = t * arcStartY; | |
} | |
} | |
else if (_x >= arcEndX) { | |
t = (_x - arcEndX)/(1 - arcEndX); | |
_y = arcEndY + t*(1 - arcEndY); | |
} | |
else { | |
if (_x >= arcCenterX) { | |
_y = arcCenterY - sqrt(sq(arcRadius) - sq(_x-arcCenterX)); | |
} | |
else { | |
_y = arcCenterY + sqrt(sq(arcRadius) - sq(_x-arcCenterX)); | |
} | |
} | |
return(_y); | |
} | |
// circular arc through a point | |
// adapted from paul bourke | |
// http://paulbourke.net/geometry/circlesphere/Circle.cpp | |
p5.Ease.prototype.circularArcThroughAPoint = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var m = {}; | |
m.centerX = 0; | |
m.centerY = 0; | |
m.dRadius = 0; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0 + Number.EPSILON; | |
var max_param_b = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
_x = constrain(_x, 0+Number.EPSILON,1-Number.EPSILON); | |
var pt1x = 0; | |
var pt1y = 0; | |
var pt2x = _a; | |
var pt2y = _b; | |
var pt3x = 1; | |
var pt3y = 1; | |
// helper functions: | |
// check if the lines defined by the given points are perpendicular | |
// to the x or y axis - used as a check before calcCircleFrom3Points() | |
// from paul bourke's Circle.cpp | |
function isPerpendicular(pt1x, pt1y, pt2x, pt2y, pt3x, pt3y) | |
{ | |
var yDelta_a = pt2y - pt1y; | |
var xDelta_a = pt2x - pt1x; | |
var yDelta_b = pt3y - pt2y; | |
var xDelta_b = pt3x - pt2x; | |
// checking whether the line of the two pts are vertical | |
if (abs(xDelta_a) <= Number.EPSILON && abs(yDelta_b) <= Number.EPSILON){ | |
return(false); | |
} | |
if (abs(yDelta_a) <= Number.EPSILON){ | |
return(true); | |
} | |
else if (abs(yDelta_b) <= Number.EPSILON){ | |
return(true); | |
} | |
else if (abs(xDelta_a)<= Number.EPSILON){ | |
return(true); | |
} | |
else if (abs(xDelta_b)<= Number.EPSILON){ | |
return(true); | |
} | |
else return(false); | |
} | |
// from paul bourke's Circle.cpp | |
function calcCircleFrom3Points(pt1x, pt1y, pt2x, pt2y, pt3x, pt3y, m) | |
{ | |
var yDelta_a = pt2y - pt1y; | |
var xDelta_a = pt2x - pt1x; | |
var yDelta_b = pt3y - pt2y; | |
var xDelta_b = pt3x - pt2x; | |
if (abs(xDelta_a) <= Number.EPSILON && abs(yDelta_b) <= Number.EPSILON){ | |
m.centerX = 0.5*(pt2x + pt3x); | |
m.centerY = 0.5*(pt1y + pt2y); | |
m.dRadius = sqrt(sq(m.centerX-pt1x) + sq(m.centerY-pt1y)); | |
return; | |
} | |
// isPerpendicular() assures that xDelta(s) are not zero | |
var aSlope = yDelta_a / xDelta_a; | |
var bSlope = yDelta_b / xDelta_b; | |
if (abs(aSlope-bSlope) <= Number.EPSILON){ // checking whether the given points are colinear. | |
return; | |
} | |
// calc center | |
m.centerX = (aSlope*bSlope*(pt1y - pt3y) + bSlope*(pt1x + pt2x)- aSlope*(pt2x+pt3x) )/(2* (bSlope-aSlope) ); | |
m.centerY = -1*(m.centerX - (pt1x+pt2x)/2)/aSlope + (pt1y+pt2y)/2; | |
m.dRadius = sqrt(sq(m.centerX-pt1x) + sq(m.centerY-pt1y)); | |
} | |
if (!isPerpendicular(pt1x,pt1y, pt2x,pt2y, pt3x,pt3y) ) calcCircleFrom3Points (pt1x,pt1y, pt2x,pt2y, pt3x,pt3y, m); | |
else if (!isPerpendicular(pt1x,pt1y, pt3x,pt3y, pt2x,pt2y) ) calcCircleFrom3Points (pt1x,pt1y, pt3x,pt3y, pt2x,pt2y, m); | |
else if (!isPerpendicular(pt2x,pt2y, pt1x,pt1y, pt3x,pt3y) ) calcCircleFrom3Points (pt2x,pt2y, pt1x,pt1y, pt3x,pt3y, m); | |
else if (!isPerpendicular(pt2x,pt2y, pt3x,pt3y, pt1x,pt1y) ) calcCircleFrom3Points (pt2x,pt2y, pt3x,pt3y, pt1x,pt1y, m); | |
else if (!isPerpendicular(pt3x,pt3y, pt2x,pt2y, pt1x,pt1y) ) calcCircleFrom3Points (pt3x,pt3y, pt2x,pt2y, pt1x,pt1y, m); | |
else if (!isPerpendicular(pt3x,pt3y, pt1x,pt1y, pt2x,pt2y) ) calcCircleFrom3Points (pt3x,pt3y, pt1x,pt1y, pt2x,pt2y, m); | |
else { | |
return 0; | |
} | |
// constrain | |
if ((m.centerX > 0) && (m.centerX < 1)){ | |
if (_a < m.centerX){ | |
m.centerX = 1; | |
m.centerY = 0; | |
m.dRadius = 1; | |
} else { | |
m.centerX = 0; | |
m.centerY = 1; | |
m.dRadius = 1; | |
} | |
} | |
//------------------ | |
var _y = 0; | |
if (_x >= m.centerX){ | |
_y = m.centerY - sqrt(sq(m.dRadius) - sq(_x-m.centerX)); | |
} | |
else{ | |
_y = m.centerY + sqrt(sq(m.dRadius) - sq(_x-m.centerX)); | |
} | |
return(_y); | |
} | |
// bezier shapers | |
// adapted from BEZMATH.PS (1993) | |
// by don lancaster, SYNERGETICS inc. | |
// http://www.tinaja.com/text/bezmath.html | |
p5.Ease.prototype.quadraticBezier = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var min_param_a = 0.0; | |
var max_param_a = 1.0; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
if (_a == 0.5){ | |
_a += Number.EPSILON; | |
} | |
// solve t from x (an inverse operation) | |
var om2a = 1.0 - 2.0*_a; | |
var t = (sqrt(_a*_a + om2a*_x) - _a)/om2a; | |
var _y = (1.0-2.0*_b)*(t*t) + (2*_b)*t; | |
return(_y); | |
} | |
// cubic bezier shaper | |
p5.Ease.prototype.cubicBezier = function(_x, _a, _b, _c, _d) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_c) _c = 0.75; // default | |
if(!_d) _d = 0.25; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
var min_param_c = 0.0 + Number.EPSILON; | |
var max_param_c = 1.0 - Number.EPSILON; | |
var min_param_d = 0.0; | |
var max_param_d = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
_c = constrain(_c, min_param_c, max_param_c); | |
_d = constrain(_d, min_param_d, max_param_d); | |
//------------------------------------------- | |
var y0a = 0.00; // initial y | |
var x0a = 0.00; // initial x | |
var y1a = _b; // 1st influence y | |
var x1a = _a; // 1st influence x | |
var y2a = _d; // 2nd influence y | |
var x2a = _c; // 2nd influence x | |
var y3a = 1.00; // final y | |
var x3a = 1.00; // final x | |
var A = x3a - 3*x2a + 3*x1a - x0a; | |
var B = 3*x2a - 6*x1a + 3*x0a; | |
var C = 3*x1a - 3*x0a; | |
var D = x0a; | |
var E = y3a - 3*y2a + 3*y1a - y0a; | |
var F = 3*y2a - 6*y1a + 3*y0a; | |
var G = 3*y1a - 3*y0a; | |
var H = y0a; | |
// Solve for t given x (using Newton-Raphelson), then solve for y given t. | |
// Assume for the first guess that t = x. | |
var currentt = _x; | |
var nRefinementIterations = 5; | |
for (var i=0; i<nRefinementIterations; i++){ | |
var currentx = A*(currentt*currentt*currentt) + B*(currentt*currentt) + C*currentt + D; | |
var currentslope = 1.0/(3.0*A*currentt*currentt + 2.0*B*currentt + C); | |
currentt -= (currentx - _x)*(currentslope); | |
currentt = constrain(currentt, 0,1.0); | |
} | |
//------------ | |
var _y = E*(currentt*currentt*currentt) + F*(currentt*currentt) + G*currentt + H; | |
return(_y); | |
} | |
// parabola through a point | |
p5.Ease.prototype.parabolaThroughAPoint = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
var A = (1-_b)/(1-_a) - (_b/_a); | |
var B = (A*(_a*_a)-_b)/_a; | |
var _y = A*(_x*_x) - B*(_x); | |
_y = constrain(_y, 0,1); | |
return(_y); | |
} | |
// damped sine wave | |
// n.b. decays to 0 at x=1 | |
// http://en.wikipedia.org/wiki/Damped_sine_wave | |
p5.Ease.prototype.dampedSinusoid = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var omega = 100*_a; | |
var lambda = -6.90775527; // ln(lambda) = 0.001 // decay constant | |
var phi = 0; | |
var e = 2.718281828459045; | |
var t = _x; | |
var _y = pow(e, lambda*t) * cos(omega*t + phi); | |
return(_y); | |
} | |
// damped sine wave (reversed) | |
p5.Ease.prototype.dampedSinusoidReverse = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var omega = 100*_a; | |
var lambda = -6.90775527; // ln(lambda) = 0.001 | |
var phi = 0; | |
var e = 2.718281828459045; | |
var t = 1.0-_x; | |
var _y = pow(e, lambda*t) * cos(omega*t + phi); | |
return(_y); | |
} | |
// cubic bezier through two points | |
p5.Ease.prototype.cubicBezierThrough2Points = function(_x, _a, _b, _c, _d) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
if(!_c) _c = 0.75; // default | |
if(!_d) _d = 0.25; // default | |
var _y = 0; | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var min_param_b = 0.0 + Number.EPSILON; | |
var max_param_b = 1.0 - Number.EPSILON; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
var x0 = 0; | |
var y0 = 0; | |
var x4 = _a; | |
var y4 = _b; | |
var x5 = _c; | |
var y5 = _d; | |
var x3 = 1; | |
var y3 = 1; | |
var x1,y1,x2,y2; // to be solved. | |
var t1 = 0.3; | |
var t2 = 0.7; | |
var B0t1 = (1-t1)*(1-t1)*(1-t1); | |
var B1t1 = 3*t1*(1-t1)*(1-t1); | |
var B2t1 = 3*t1*t1*(1-t1); | |
var B3t1 = t1*t1*t1; | |
var B0t2 = (1-t2)*(1-t2)*(1-t2); | |
var B1t2 = 3*t2*(1-t2)*(1-t2);; | |
var B2t2 = 3*t2*t2*(1-t2);; | |
var B3t2 = t2*t2*t2; | |
var ccx = x4 - x0*B0t1 - x3*B3t1; | |
var ccy = y4 - y0*B0t1 - y3*B3t1; | |
var ffx = x5 - x0*B0t2 - x3*B3t2; | |
var ffy = y5 - y0*B0t2 - y3*B3t2; | |
x2 = (ccx - (ffx*B1t1)/B1t2) / (B2t1 - (B1t1*B2t2)/B1t2); | |
y2 = (ccy - (ffy*B1t1)/B1t2) / (B2t1 - (B1t1*B2t2)/B1t2); | |
x1 = (ccx - x2*B2t1) / B1t1; | |
y1 = (ccy - y2*B2t1) / B1t1; | |
x1 = constrain(x1, 0+Number.EPSILON,1-Number.EPSILON); | |
x2 = constrain(x2, 0+Number.EPSILON,1-Number.EPSILON); | |
_y = this.cubicBezier (_x, x1,y1, x2,y2); | |
_y = constrain(_y,0,1); | |
return(_y); | |
} | |
// double circular ogee | |
p5.Ease.prototype.doubleCircularOgee = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var min_param_a = 0.0; | |
var max_param_a = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
var _y = 0; | |
if (_x<=_a){ | |
_y = sqrt(sq(_a) - sq(_x-_a)); | |
} | |
else { | |
_y = 1 - sqrt(sq(1-_a) - sq(_x-_a)); | |
} | |
return(_y); | |
} | |
// double squircular ogee | |
p5.Ease.prototype.doubleSquircularOgee = function(_x, _a, _n) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_n) _n = 3; // default | |
var min_param_a = 0.0; | |
var max_param_a = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
var pown = 2.0 * _n; | |
var _y = 0; | |
if (_x<=_a){ | |
_y = pow( pow(_a,pown) - pow(_x-_a, pown), 1.0/pown); | |
} | |
else { | |
_y = 1.0 - pow( pow(1-_a,pown) - pow(_x-_a, pown), 1.0/pown); | |
} | |
return(_y); | |
} | |
// generalized combo sigmoid / logit function | |
p5.Ease.prototype.generalSigmoidLogitCombo = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var _y = 0; | |
if (_a < 0.5){ | |
// Logit | |
var dy = _b - 0.5; | |
_y = dy + this.normalizedLogit (_x, 1.0-(2.0*_a)); | |
} else { | |
// Sigmoid | |
var dx = _b - 0.5; | |
_y = this.normalizedLogitSigmoid (_x+dx, (2.0*(_a-0.5))); | |
} | |
_y = constrain(_y, 0, 1); | |
return(_y); | |
} | |
// normalized logistic sigmoid function | |
p5.Ease.prototype.normalizedLogitSigmoid = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var emph = 5.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_a = (1.0/(1.0-_a) - 1.0); | |
_a = emph * _a; | |
var _y = 1.0 / (1.0 + exp(0 - (_x-0.5)*_a )); | |
var miny = 1.0 / (1.0 + exp( 0.5*_a )); | |
var maxy = 1.0 / (1.0 + exp( -0.5*_a )); | |
_y = map(_y, miny, maxy, 0, 1); | |
return(_y); | |
} | |
// logit function | |
// https://en.wikipedia.org/wiki/Logit | |
p5.Ease.prototype.normalizedLogit = function(_x, _a) | |
{ | |
if(!_a) _a = 0.25; // default | |
var min_param_a = 0.0 + Number.EPSILON; | |
var max_param_a = 1.0 - Number.EPSILON; | |
var emph = 5.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_a = (1/(1-_a) - 1); | |
_a = emph * _a; | |
var minx = 1.0 / (1.0 + exp( 0.5*_a )); | |
var maxx = 1.0 / (1.0 + exp( -0.5*_a )); | |
_x = map(_x, 0,1, minx, maxx); | |
var _y = log (_x / (1.0 - _x)) ; | |
_y *= 1.0/_a; | |
_y += 0.5; | |
_y = constrain (_y, 0, 1); | |
return(_y); | |
} | |
// quartic easing function | |
p5.Ease.prototype.generalizedQuartic = function(_x, _a, _b) | |
{ | |
if(!_a) _a = 0.25; // default | |
if(!_b) _b = 0.75; // default | |
var min_param_a = 0.0; | |
var max_param_a = 1.0; | |
var min_param_b = 0.0; | |
var max_param_b = 1.0; | |
_a = constrain(_a, min_param_a, max_param_a); | |
_b = constrain(_b, min_param_b, max_param_b); | |
_a = 1.0-_a; | |
var _w = (1-2*_a)*(_x*_x) + (2*_a)*_x; | |
var _y = (1-2*_b)*(_w*_w) + (2*_b)*_w; | |
return(_y); | |
} | |
// boxcar function (normalized heaviside step function) | |
// http://mathworld.wolfram.com/BoxcarFunction.html | |
// https://en.wikipedia.org/wiki/Heaviside_step_function | |
p5.Ease.prototype.boxcar = function(_x) | |
{ | |
return(_x>=0.5); | |
} | |
// list algorithms | |
p5.Ease.prototype.listAlgos = function() { | |
var _styles = new Array(); | |
for(var i in this.__proto__) { | |
var _t = true; | |
if(i=="listAlgos") _t=false; | |
else if(i=="fillArray") _t=false; | |
else if(i=="fillFloat32Array") _t=false; | |
else if(i=="fillFloat64Array") _t=false; | |
if(_t) _styles.push(i); | |
} | |
return(_styles); | |
} | |
// array xfer (for pre-rendered use) | |
p5.Ease.prototype.fillArray = function(_algo, _len, _args) { | |
var _dest = new Array(_len); | |
for(var i = 0;i<_len;i++) | |
{ | |
var _p = i/(_len-1); // 0.-1. | |
_dest[i] = this[_algo](_p, _args); | |
} | |
return(_dest); | |
} | |
// array xfer (for pre-rendered use) | |
p5.Ease.prototype.fillFloat32Array = function(_algo, _len, _args) { | |
var _dest = new Float32Array(_len); | |
for(var i = 0;i<_len;i++) | |
{ | |
var _p = i/(_len-1); // 0.-1. | |
_dest[i] = this[_algo](_p, _args); | |
} | |
return(_dest); | |
} | |
// array xfer (for pre-rendered use) | |
p5.Ease.prototype.fillFloat64Array = function(_algo, _len, _args) { | |
var _dest = new Float64Array(_len); | |
for(var i = 0;i<_len;i++) | |
{ | |
var _p = i/(_len-1); // 0.-1. | |
_dest[i] = this[_algo](_p, _args); | |
} | |
return(_dest); | |
} | |
// ============================================================================= | |
// p5.ArrayEval | |
// ============================================================================= | |
/** | |
* Base class for an array evaluator | |
* | |
* @class p5.Gen | |
* @constructor | |
*/ | |
p5.ArrayEval = function() { | |
// | |
// this object implements an 'eval'-style | |
// equation evaluator across n-dimensional arrays. | |
// insired by the 'exprfill' / [jit.expr] functionality | |
// in Max/MSP. | |
// | |
// note that the javascript eval() function is *not* | |
// considered to be particularly secure, as it can | |
// easily be used to execute arbitrary code. | |
// | |
// see here for an exhausting discussion of the issue: | |
// https://stackoverflow.com/questions/86513/why-is-using-the-javascript-eval-function-a-bad-idea | |
// | |
// the p5.ArrayEval object has three methods to create | |
// 1-, 2-, and 3- dimensional javascript arrays based on | |
// a formula encoded in a string. | |
// the letters u, v, and w will be replaced with various | |
// 'normal' maps based on their cell positions. | |
// * u, v, w will unwrap to 0. to 1. across dimension 1, 2, and 3 | |
// * su, sv, sw will unwrap to -1. to 1. | |
// * cu, cv, cw will unwrap to their integer position in the array | |
// * du, dv, dw gets replaced with the length of the array in that dimension | |
// | |
// the object returns an array that solves the equation, so... | |
// if you instantiate an object... | |
// var e = new p5.ArrayEval(); | |
// then... | |
// e.eval('u', 40); | |
// will return a one-dimensional array with 40 values from 0. to 1. | |
// and... | |
// e.eval2d('su*sv', 20, 20); | |
// will return a two-dimensional array with 20x20 values from -1. to 1. multiplied together. | |
// | |
// because the eval() is run in the browser, any code included will add | |
// functionality to the p5.ArrayEval object, e.g. p5.js math functions | |
// (sin(), cos(), etc.) will work correctly. | |
// | |
this.version = 0.01; // just some crap for constructor | |
var that = this; // some bullshit | |
// global dims | |
var l1 = 0; | |
var l2 = 0; | |
var l3 = 0; | |
}; // end p5.ArrayEval constructor | |
// array return - 1d | |
p5.ArrayEval.prototype.eval = function(_evalstr, _l1) { | |
this.l1 = _l1; // make global | |
var multi = 0; // default one output | |
var e; | |
if(Array.isArray(_evalstr)) multi = 1; // array per result | |
var _dest; | |
if(multi) _dest = createArray(_l1, _evalstr.length); | |
else _dest = createArray(_l1); | |
// string expansion: | |
// u unwraps to 0-1 | |
// su unwraps to -1 to 1 | |
// cu unwraps to cell value (0 to dim-1) | |
// du unwraps to a constant representing the dimension (size) | |
if(multi) | |
{ | |
for(e in _evalstr) | |
{ | |
_evalstr[e] = _evalstr[e].replace(/cu/g, "i"); | |
_evalstr[e] = _evalstr[e].replace(/du/g, "l1"); | |
_evalstr[e] = _evalstr[e].replace(/su/g, "(i/(l1-1)*2.-1.)"); | |
_evalstr[e] = _evalstr[e].replace(/u/g, "(i/(l1-1))"); | |
} | |
} | |
else { | |
_evalstr = _evalstr.replace(/cu/g, "i"); | |
_evalstr = _evalstr.replace(/du/g, "l1"); | |
_evalstr = _evalstr.replace(/su/g, "(i/(l1-1)*2.-1.)"); | |
_evalstr = _evalstr.replace(/u/g, "(i/(l1-1))"); | |
} | |
//console.log(_evalstr); | |
for(var i = 0;i<this.l1;i++) | |
{ | |
if(multi) | |
{ | |
for(e = 0;e<_evalstr.length;e++) | |
{ | |
_dest[i][e] = eval('with(this) { ' + _evalstr[e] + ' }'); | |
} | |
} | |
else _dest[i] = eval('with(this) { ' + _evalstr + ' }'); | |
} | |
return(_dest); | |
} | |
// function synonym | |
p5.ArrayEval.prototype.eval1d = function(_evalstr, _l1) | |
{ | |
return(this.eval(_evalstr, _l1)); | |
} | |
// array return - 2d | |
p5.ArrayEval.prototype.eval2d = function(_evalstr, _l1, _l2) { | |
this.l1 = _l1; // make global | |
this.l2 = _l2; // make global | |
var multi = 0; // default one output | |
var e; | |
if(Array.isArray(_evalstr)) multi = 1; // array per result | |
var _dest; | |
if(multi) _dest = createArray(_l1, _l2, _evalstr.length); | |
else _dest = createArray(_l1, _l2); | |
// string expansion: | |
// u unwraps to 0-1 | |
// su unwraps to -1 to 1 | |
// cu unwraps to cell value (0 to dim-1) | |
// du unwraps to a constant representing the dimension (size) | |
// v unwraps to 0-1 | |
// sv unwraps to -1 to 1 | |
// cv unwraps to cell value (0 to dim-1) | |
// dv unwraps to a constant representing the dimension (size) | |
if(multi) | |
{ | |
for(e in _evalstr) | |
{ | |
_evalstr[e] = _evalstr[e].replace(/cu/g, "i"); | |
_evalstr[e] = _evalstr[e].replace(/du/g, "l1"); | |
_evalstr[e] = _evalstr[e].replace(/su/g, "(i/(l1-1)*2.-1.)"); | |
_evalstr[e] = _evalstr[e].replace(/u/g, "(i/(l1-1))"); | |
_evalstr[e] = _evalstr[e].replace(/cv/g, "j"); | |
_evalstr[e] = _evalstr[e].replace(/dv/g, "l2"); | |
_evalstr[e] = _evalstr[e].replace(/sv/g, "(j/(l2-1)*2.-1.)"); | |
_evalstr[e] = _evalstr[e].replace(/v/g, "(j/(l2-1))"); | |
} | |
} | |
else { | |
_evalstr = _evalstr.replace(/cu/g, "i"); | |
_evalstr = _evalstr.replace(/du/g, "l1"); | |
_evalstr = _evalstr.replace(/su/g, "(i/(l1-1)*2.-1.)"); | |
_evalstr = _evalstr.replace(/u/g, "(i/(l1-1))"); | |
_evalstr = _evalstr.replace(/cv/g, "j"); | |
_evalstr = _evalstr.replace(/dv/g, "l2"); | |
_evalstr = _evalstr.replace(/sv/g, "(j/(l2-1)*2.-1.)"); | |
_evalstr = _evalstr.replace(/v/g, "(j/(l2-1))"); | |
} | |
//console.log(_evalstr); | |
for(var i = 0;i<this.l1;i++) | |
{ | |
for(var j = 0;j<this.l2;j++) | |
{ | |
if(multi) | |
{ | |
for(e = 0;e<_evalstr.length;e++) | |
{ | |
_dest[i][j][e] = eval('with(this) { ' + _evalstr[e] + ' }'); | |
} | |
} | |
else _dest[i][j] = eval('with(this) { ' + _evalstr + ' }'); | |
} | |
} | |
return(_dest); | |
} | |
// array return - 3d | |
p5.ArrayEval.prototype.eval3d = function(_evalstr, _l1, _l2, _l3) { | |
this.l1 = _l1; // make global | |
this.l2 = _l2; // make global | |
this.l3 = _l3; // make global | |
var multi = 0; // default one output | |
var e; | |
if(Array.isArray(_evalstr)) multi = 1; // array per result | |
var _dest; | |
if(multi) _dest = createArray(_l1, _l2, _l3, _evalstr.length); | |
else _dest = createArray(_l1, _l2, _l3); | |
// string expansion: | |
// u unwraps to 0-1 | |
// su unwraps to -1 to 1 | |
// cu unwraps to cell value (0 to dim-1) | |
// du unwraps to a constant representing the dimension (size) | |
// v unwraps to 0-1 | |
// sv unwraps to -1 to 1 | |
// cv unwraps to cell value (0 to dim-1) | |
// dv unwraps to a constant representing the dimension (size) | |
// w unwraps to 0-1 | |
// sw unwraps to -1 to 1 | |
// cw unwraps to cell value (0 to dim-1) | |
// dw unwraps to a constant representing the dimension (size) | |
if(multi) | |
{ | |
for(e in _evalstr) | |
{ | |
_evalstr[e] = _evalstr[e].replace(/cu/g, "i"); | |
_evalstr[e] = _evalstr[e].replace(/du/g, "l1"); | |
_evalstr[e] = _evalstr[e].replace(/su/g, "(i/(l1-1)*2.-1.)"); | |
_evalstr[e] = _evalstr[e].replace(/u/g, "(i/(l1-1))"); | |
_evalstr[e] = _evalstr[e].replace(/cv/g, "j"); | |
_evalstr[e] = _evalstr[e].replace(/dv/g, "l2"); | |
_evalstr[e] = _evalstr[e].replace(/sv/g, "(j/(l2-1)*2.-1.)"); | |
_evalstr[e] = _evalstr[e].replace(/v/g, "(j/(l2-1))"); | |
_evalstr[e] = _evalstr[e].replace(/cw/g, "k"); | |
_evalstr[e] = _evalstr[e].replace(/dw/g, "l3"); | |
_evalstr[e] = _evalstr[e].replace(/sw/g, "(k/(l3-1)*2.-1.)"); | |
_evalstr[e] = _evalstr[e].replace(/w/g, "(k/(l3-1))"); | |
} | |
} | |
else { | |
_evalstr = _evalstr.replace(/cu/g, "i"); | |
_evalstr = _evalstr.replace(/du/g, "l1"); | |
_evalstr = _evalstr.replace(/su/g, "(i/(l1-1)*2.-1.)"); | |
_evalstr = _evalstr.replace(/u/g, "(i/(l1-1))"); | |
_evalstr = _evalstr.replace(/cv/g, "j"); | |
_evalstr = _evalstr.replace(/dv/g, "l2"); | |
_evalstr = _evalstr.replace(/sv/g, "(j/(l2-1)*2.-1.)"); | |
_evalstr = _evalstr.replace(/v/g, "(j/(l2-1))"); | |
_evalstr = _evalstr.replace(/cw/g, "k"); | |
_evalstr = _evalstr.replace(/dw/g, "l3"); | |
_evalstr = _evalstr.replace(/sw/g, "(k/(l3-1)*2.-1.)"); | |
_evalstr = _evalstr.replace(/w/g, "(k/(l3-1))"); | |
} | |
//console.log(_evalstr); | |
for(var i = 0;i<this.l1;i++) | |
{ | |
for(var j = 0;j<this.l2;j++) | |
{ | |
for(var k = 0;k<this.l3;k++) | |
{ | |
if(multi) | |
{ | |
for(e = 0;e<_evalstr.length;e++) | |
{ | |
_dest[i][j][k][e] = eval('with(this) { ' + _evalstr[e] + ' }'); | |
} | |
} | |
else _dest[i][j][k] = eval('with(this) { ' + _evalstr + ' }'); | |
} | |
} | |
} | |
return(_dest); | |
} | |
// ============================================================================= | |
// p5.Filt | |
// ============================================================================= | |
/** | |
* Base class for a time-domain filter | |
* | |
* @class p5.Filt | |
* @constructor | |
*/ | |
p5.Filt = function(_fs) { | |
// | |
// this object implements time-domain filtering based on | |
// robert bristow-johnson's cookbook formulae for | |
// biquadratic (2 pole, 2 zero) filters : | |
// http://www.musicdsp.org/files/Audio-EQ-Cookbook.txt | |
// Copyright (C) 2003 Robert Bristow-Johnson | |
// | |
// we are using his general 2p2z / biquad formula: | |
// y[n] = (b0/a0)*x[n] + (b1/a0)*x[n-1] + (b2/a0)*x[n-2] - (a1/a0)*y[n-1] - (a2/a0)*y[n-2] | |
// | |
// this is the same algorithm used in the [biquad~] object in PureData and Max/MSP, | |
// the [2p2z~] object in Max/FTS, the BiQuad.cpp ugen in the STK (ChucK, etc.), | |
// SOS in SuperCollider, the biquada opcode in CSOUND, etc. etc. etc. | |
// | |
// the biquadratic filter equation is pretty standard, insofar as you can | |
// construct any 'simple' filter (lowpass, highpass, bandpass, bandstop, | |
// allpass, etc.) with independent gain controls (coefficients) for the | |
// input (x[n]), (usually) the overall output (y[n]), and four samples | |
// of memory - the two previous input samples (x[n-1], x[n-2]), and the | |
// two previous output samples (y[n-1], y[n-2]). | |
// | |
// so this filter has both 2nd-order FIR (feedforward) and 2nd-order IIR | |
// (feedback) capabilities. you set the coefficients by running some | |
// trig against your desired filter characteristics... | |
// * type | |
// * cutoff / center frequency | |
// * Q / resonance | |
// * gain (for some filters) | |
// ...and the known sample rate (Fs). | |
// | |
// more complex filters can be constructed by 'cascading' biquad filters | |
// in series, e.g. for butterworth / chebyshev filters that require a | |
// more flat frequency response than a simple biquad can offer. | |
// | |
// you can find lots of info about this filter by searching for | |
// 'biquad' on your internet machine. however... | |
// | |
// some people, they will flip their 'a' and 'b' coefficients. | |
// some people, they will skip a0 and use 5 coefficients for biquad formulae. | |
// some people, they like to go out dancing. | |
// | |
// p5.Filt is offered here as a module to allow for filter design | |
// independent of the Web Audio framework used by p5.sound / Tone.js. | |
// there are lots of other things one might want to 'filter' besides sound. | |
// the defaults provided here are against the nominal graphics frame rate | |
// for p5.js (60Hz), giving an effective nyquist (upper frequency limit) | |
// of 30Hz. try running a random() generator through it, or using it | |
// to smooth out a noisy signal coming from a sensor or a network API. | |
// it wil process input sample-by-sample through the tick() method or | |
// process arrays vector-by-vector through the process() method. | |
// | |
if(!_fs) _fs = 60.; // nominal p5 default framerate | |
this.version = 0.01; // just some crap for constructor | |
var that = this; // some bullshit | |
// biquad / 2p2z coefficients | |
this.a0 = 1.; // denominator gain for filter output (y[n] term) | |
this.b0 = 1.; // gain for current input (x[n] term) | |
this.a1 = 0.; // gain for previous input (x[n-1] term) | |
this.b1 = 0.; // gain for previous previous input (x[n-2] term) | |
this.a2 = 0.; // gain for previous output (y[n-1] term) | |
this.b2 = 0.; // gain for previous previous output (y[n-2] term) | |
// sample memory | |
this.xn = 0.; // x[n] (input) | |
this.yn = 0.; // y[n] (output) | |
this.xpn = 0.; // x[n-1] (previous input) | |
this.ypn = 0.; // y[n-1] (previous output) | |
this.xppn = 0.; // x[n-2] (previous previous input) | |
this.yppn = 0.; // y[n-2] (previous previous output) | |
// parameters | |
this.fs = _fs; // sampling rate | |
this.type = "LPF"; // default to lowpass filter | |
this.f0 = this.fs/4.; // center/cutoff frequency; default to fs/4 (half nyquist) | |
this.dB = 0.; // gain for peaking / shelving | |
this.Q = 1.; // width / resonance of filter | |
// intermediates | |
this.A; // amplitude | |
this.w0; // filter increment in radians | |
this.cw0; // cosine of w0 - precompute | |
this.sw0; // sine of w0 - precompute | |
this.alpha; // alpha term - precompute | |
this.soff; // shelving offset - precompute | |
// compute coefficients based on default parameters | |
this.precalc(); | |
}; // end p5.Filt constructor | |
// define the filter characteristics all at once. | |
p5.Filt.prototype.set = function(_type, _f0, _Q, _dB) | |
{ | |
if(_type) this.type = _type; | |
if(_f0) this.f0 = _f0; | |
if(_Q) this.Q = _Q; | |
if(_dB) this.dB = _dB; // only matters for shelving filters | |
this.precalc(); | |
} | |
// set the sampling rate (fs) of the filter. | |
p5.Filt.prototype.setFs = function(_fs) | |
{ | |
if(_fs) this.fs = _fs; | |
this.precalc(); | |
} | |
// set the type of the filter. | |
p5.Filt.prototype.setType = function(_type) | |
{ | |
if(_type) this.type = _type; | |
this.precalc(); | |
} | |
// set the cutoff/center frequency. | |
p5.Filt.prototype.setFreq = function(_f0) | |
{ | |
if(_f0) this.f0 = _f0; | |
this.precalc(); | |
} | |
// set the Q(uality) of the filter. | |
p5.Filt.prototype.setQ = function(_Q) | |
{ | |
if(_Q) this.Q = _Q; | |
this.precalc(); | |
} | |
// set the gain (in decibels). | |
p5.Filt.prototype.setGain = function(_dB) | |
{ | |
if(_dB) this.dB = _dB; | |
this.precalc(); | |
} | |
// set the bandwidth in Hz (inverse of Q). | |
p5.Filt.prototype.setBW = function(_bw) | |
{ | |
// technically... | |
// this.Q = 1.0/(2*Math.sinh(log(2)/2*_bw*this.w0/this.sw0)); | |
// but YOLO... | |
if(_bw) this.Q = this.f0/_bw; | |
this.precalc(); | |
} | |
// process multiple values as a single vector; | |
// n.b. filter will retain memory... use a clear() | |
// beforehand if you want the filter zeroed. | |
p5.Filt.prototype.process = function(_x) | |
{ | |
var _y; // output | |
// figure out input type | |
if(Array.isArray(_x)) _y = new Array(_x.length); | |
if(_x.constructor == Float32Array) _y = new Float32Array(_x.length); | |
if(_x.constructor == Float64Array) _y = new Float64Array(_x.length); | |
for(var i in _x) | |
{ | |
_y[i] = this.tick(_x[i]); | |
} | |
// output | |
return(_y); | |
} | |
// process sample-by-sample, STK style. | |
p5.Filt.prototype.tick = function(_x) | |
{ | |
// input | |
this.xn = _x; | |
// biquad - the only line in this whole situation that really matters: | |
this.yn = (this.b0/this.a0)*this.xn + (this.b1/this.a0)*this.xpn + (this.b2/this.a0)*this.xppn - (this.a1/this.a0)*this.ypn - (this.a2/this.a0)*this.yppn; | |
// shift | |
this.xppn = this.xpn; | |
this.xpn = this.xn; | |
this.yppn = this.ypn; | |
this.ypn = this.yn; | |
// output | |
return(this.yn); | |
} | |
// clear biquad memory - | |
// useful if the filter becomes unstable and you start getting NaNs as output. | |
p5.Filt.prototype.clear = function() | |
{ | |
// clear samples | |
this.xn = 0; // x[n] (input) | |
this.yn = 0; // y[n] (output) | |
this.xpn = 0; // x[n-1] | |
this.ypn = 0; // y[n-1] | |
this.xppn = 0; // x[n-2] | |
this.yppn = 0; // y[n-2] | |
} | |
// set coefficients 'by hand' - | |
// useful for cascade (butterworth / chebyshev) filtering | |
p5.Filt.prototype.coeffs = function(_a0, _b0, _b1, _b2, _a1, _a2) | |
{ | |
if(arguments.length!=6) | |
{ | |
console.log("p5.Filt needs six coefficients for raw biquad:"); | |
console.log(" a0 -> denominator gain for filter (y[n] term)."); | |
console.log(" b0 -> gain for current input (x[n] term)."); | |
console.log(" b1 -> gain for previous input (x[n-1] term)."); | |
console.log(" b2 -> gain for previous previous input (x[n-2] term)."); | |
console.log(" a1 -> gain for previous output (y[n-1] term)."); | |
console.log(" a2 -> gain for previous previous output (y[n-2] term)."); | |
console.log("when working with 5-coefficient biquad formulae set a0 to 1.0."); | |
console.log("n.b. some systems will refer to y terms as 'b' and x terms as 'a'."); | |
} | |
else | |
{ | |
this.a0 = _a0; // y[n] (overall gain) | |
this.b0 = _b0; // x[n] | |
this.b1 = _b1; // x[n-1] | |
this.b2 = _b2; // x[n-2] | |
this.a1 = _a1; // y[n-1] | |
this.a2 = _a2; // y[n-2] | |
} | |
} | |
// calculate filter coefficients from parameters | |
p5.Filt.prototype.precalc = function() | |
{ | |
// intermediates | |
this.A = sqrt(pow(10, this.dB/20)); // amplitude | |
this.w0 = 2*PI*this.f0/this.fs; // filter increment in radians | |
this.cw0 = cos(this.w0); // cosine of w0 - precompute | |
this.sw0 = sin(this.w0); // sine of w0 - precompute | |
this.alpha = sin(this.w0)/(2*this.Q); // alpha term - precompute | |
this.soff = 2*sqrt(this.A)*this.alpha; // shelving offset - precompute | |
switch(this.type) { | |
case "LPF": | |
case "lowpass": | |
this.b0 = (1 - this.cw0)/2; | |
this.b1 = 1 - this.cw0; | |
this.b2 = (1 - this.cw0)/2; | |
this.a0 = 1 + this.alpha; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha; | |
break; | |
case "HPF": | |
case "highpass": | |
this.b0 = (1 + this.cw0)/2; | |
this.b1 = -(1 + this.cw0); | |
this.b2 = (1 + this.cw0)/2; | |
this.a0 = 1 + this.alpha; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha; | |
break; | |
case "BPF": | |
case "bandpass": | |
this.b0 = this.sw0/2; | |
this.b1 = 0; | |
this.b2 = -this.sw0/2; | |
this.a0 = 1 + this.alpha; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha; | |
break; | |
case "BPF0": // constant 0 peak gain | |
case "resonant": | |
this.b0 = this.alpha; | |
this.b1 = 0; | |
this.b2 = -this.alpha; | |
this.a0 = 1 + this.alpha; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha; | |
break; | |
case "notch": | |
case "bandreject": | |
case "bandstop": | |
this.b0 = 1; | |
this.b1 = -2 * this.cw0; | |
this.b2 = 1; | |
this.a0 = 1 + this.alpha; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha; | |
break; | |
case "APF": | |
case "allpass": | |
this.b0 = 1 - this.alpha; | |
this.b1 = -2 * this.cw0; | |
this.b2 = 1 + this.alpha; | |
this.a0 = 1 + this.alpha; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha; | |
break; | |
case "peakingEQ": | |
case "peaknotch": | |
this.b0 = 1 + this.alpha*this.A; | |
this.b1 = -2 * this.cw0; | |
this.b2 = 1 - this.alpha*this.A; | |
this.a0 = 1 + this.alpha/this.A; | |
this.a1 = -2 * this.cw0; | |
this.a2 = 1 - this.alpha/this.A; | |
break; | |
case "lowShelf": | |
case "lowshelf": | |
this.b0 = this.A*((this.A+1) - (this.A-1)*this.cw0 + this.soff); | |
this.b1 = 2*this.A*((this.A-1) - (this.A+1)*this.cw0); | |
this.b2 = this.A*((this.A+1) - (this.A-1)*this.cw0 - this.soff); | |
this.a0 = (this.A+1) + (this.A-1)*this.cw0 + this.soff; | |
this.a1 = -2*((this.A-1) + (this.A+1)*this.cw0); | |
this.a2 = (this.A+1) + (this.A-1)*this.cw0 - this.soff; | |
break; | |
case "highShelf": | |
case "highshelf": | |
this.b0 = this.A*((this.A+1) + (this.A-1)*this.cw0 + this.soff); | |
this.b1 = -2*this.A*((this.A-1) + (this.A+1)*this.cw0); | |
this.b2 = this.A*((this.A+1) + (this.A-1)*this.cw0 - this.soff); | |
this.a0 = (this.A+1) - (this.A-1)*this.cw0 + this.soff; | |
this.a1 = 2*((this.A-1) - (this.A+1)*this.cw0); | |
this.a2 = (this.A+1) - (this.A-1)*this.cw0 - this.soff; | |
break; | |
default: // pass through | |
this.b0 = 1.; | |
this.b1 = 0.; | |
this.b2 = 0.; | |
this.a0 = 1.; | |
this.a1 = 0.; | |
this.a2 = 0.; | |
break; | |
} | |
} | |
// ============================================================================= | |
// p5.FastFourierTransform | |
// ============================================================================= | |
/** | |
* Base class for an FFT (non-signal) module | |
* | |
* @class p5.FastFourierTranform | |
* @constructor | |
*/ | |
p5.FastFourierTransform = function(_bufsize, _fs, _hopsize) { | |
// | |
// this object implements a simple FFT (fast fourier transform) | |
// module using the 1965 cooley-tukey FFT algorithm: | |
// http://www.ams.org/journals/mcom/1965-19-090/S0025-5718-1965-0178586-1/S0025-5718-1965-0178586-1.pdf | |
// | |
// there's a great breakdown of how the FFT algorithm works here: | |
// https://www.cs.cmu.edu/afs/andrew/scs/cs/15-463/2001/pub/www/notes/fourier/fourier.pdf | |
// | |
// the code below is adapted from the FFT module in dsp.js written by @corbanbrook : | |
// https://github.com/corbanbrook/dsp.js/ | |
// Copyright (c) 2010 Corban Brook, released under the MIT license | |
// | |
// p5.FastFourierTransform runs completely independent of the Web Audio | |
// framework used by p5.sound / Tone.js, which allows you to analyze | |
// and synthesize frequency-domain data regardless of whether it counts | |
// as 'sound' or runs at audio rate. | |
// | |
// how many samples are we analyzing? | |
// should be a power of 2. | |
this.bufferSize = _bufsize ? _bufsize : 512; | |
// what's our hopsize? | |
// if nothing's there, set it to the FFT size. | |
this.hopSize = _hopsize ? _hopsize : this.bufferSize; | |
// fourier transforms are 'rate' agnostic... | |
// the algorithm doesn't care how fast the signal is, so | |
// the sampling rate here is simply to calculate | |
// getBandFrequency() as a utility function so we can | |
// find out, e.g. the center frequency of a specific FFT bin. | |
this.sampleRate = _fs ? _fs : 60; | |
// FFT fundamental / bandwidth: used for | |
// getBandFrequency() and calculateFrequency() | |
this.bandwidth = this.sampleRate / this.bufferSize; | |
// data arrays for the raw real/imaginary FFT data. | |
this.real = new Float64Array(this.bufferSize); | |
this.imag = new Float64Array(this.bufferSize); | |
// spectrum compute flag and data arrays for the magnitude / phase. | |
// note that the spectrum (correctly) only needs a half frame of | |
// data, as the FFT output is mirrored for bins above nyquist (SR/2). | |
this.doSpectrum = true; | |
this.magnitude = new Float64Array(this.bufferSize/2); | |
this.phase = new Float64Array(this.bufferSize/2); | |
// compute flag and data arrays for instantaneous frequency estimation | |
this.doFrequency = false; | |
this.runningphase = new Float64Array(this.bufferSize/2); | |
this.previousphase = new Float64Array(this.bufferSize/2); | |
this.frequency = new Float64Array(this.bufferSize/2); | |
// the calculateSpectrum() method will stash the 'loudest' | |
// bin, as well as its value. | |
this.peakBand = 0; // peak band (FFT bin) | |
this.peak = 0.; // peak value. | |
// calculate the FFT 'reverse table'. | |
// the reverse table is a super groovy hack that helps put the | |
// 'fast' in fast fourier transform. | |
// | |
// to quote jim noxon at texas instruments : | |
// "The purpose of having this table is due to a quirk of the FFT algorithm. | |
// As it turns out, the indexing done in the FFT algorithm (and the IFFT too) | |
// relates the input index to the output index in a manner where reversing | |
// the bits of the input index creates the appropriate index for the output. | |
// As you can see the inner for() loop would have to be run for every | |
// input to output index operation of the FFT algorithm so by creating a table | |
// up front, the FFT algorithm can be run multiple times and only needs to | |
// be calculated once. Further, if this table is calculated at compile time, | |
// then there is no dynamic initialization necessary at all. Now, it is | |
// simply a look up into the table using the input index to generate the | |
// output index. Some DSPs have a special addressing mode that does this | |
// automatically eliminating the need for the table all together." | |
// | |
this.reverseTable = new Uint32Array(this.bufferSize); | |
var limit = 1; | |
var bit = this.bufferSize >> 1; | |
var i; | |
while (limit < this.bufferSize) { | |
for (i = 0; i < limit; i++) { | |
this.reverseTable[i + limit] = this.reverseTable[i] + bit; | |
} | |
limit = limit << 1; | |
bit = bit >> 1; | |
} | |
// precompute sine and cosine sampling increment (SI) arrays. | |
this.sinTable = new Float64Array(this.bufferSize); | |
this.cosTable = new Float64Array(this.bufferSize); | |
for (i = 0; i < this.bufferSize; i++) { | |
// we never call index 0, which is NaN | |
this.sinTable[i] = sin(-PI/i); | |
this.cosTable[i] = cos(-PI/i); | |
} | |
}; // end p5.FastFourierTransform constructor | |
// query the center frequency of a specific FFT band (e.g. the peakBand). | |
// remember that this is the frequency of the *filter*, not necessarily | |
// the signal that is actuating the filter. to find that out you would | |
// compute running phase off of sequential frames of data to see whether | |
// the phase in the bin is rising or falling, and use that to compute | |
// the frequency differential between the band and the actuating signal | |
// (see below... calculateFrequency(), for an implementation). | |
p5.FastFourierTransform.prototype.getBandFrequency = function(_index) { | |
return this.bandwidth * _index; | |
} | |
// calculate the spectrum (magnitude / phase) from the cartesian | |
// (real / imaginary - x / y) raw FFT output. this is basically a | |
// pythagorean transformation, with the magnitudes scaled to ranges | |
// based on the FFT size. | |
p5.FastFourierTransform.prototype.calculateSpectrum = function() { | |
var rval, ival, mag, phase; | |
var bSi = 2 / this.bufferSize; // scaling factor for magnitudes | |
this.peakBand = 0; // reset each spectral frame | |
this.peak = 0; // reset each spectral frame | |
for (var i = 0, N = this.bufferSize/2; i < N; i++) { | |
rval = this.real[i]; // x | |
ival = this.imag[i]; // y | |
mag = bSi * sqrt(rval * rval + ival * ival); | |
phase = atan2(ival, rval); | |
if (mag > this.peak) { | |
this.peakBand = i; | |
this.peak = mag; | |
} | |
this.magnitude[i] = mag; | |
this.phase[i] = phase; | |
} | |
if(this.doFrequency) this.calculateFrequency(); | |
} | |
// computes instantaneous frequency based on running phase. | |
p5.FastFourierTransform.prototype.calculateFrequency = function() { | |
var temp = Array.from(this.phase); | |
this.runningphase = subtractArray(temp, this.previousphase); // compute running phase | |
this.runningphase = addArray(this.runningphase,TWO_PI+PI); | |
this.runningphase = moduloArray(this.runningphase, TWO_PI); | |
this.runningphase = subtractArray(this.runningphase, PI); | |
this.previousphase = temp; | |
// compute frequencies: | |
for(i in this.frequency) | |
{ | |
this.frequency[i] = this.bandwidth*i + this.runningphase[i]*this.sampleRate/(TWO_PI*this.hopSize); | |
} | |
} | |
// performs a forward FFT transform on a sample buffer. | |
// this converts the data from the time domain into the frequency domain. | |
// fills up the real and imag buffers in the object's data structure, | |
// and also runs calculateSpectrum() to get the magnitude and phase. | |
p5.FastFourierTransform.prototype.forward = function(_buffer) { | |
var k = floor(log(this.bufferSize) / Math.LN2); | |
if (pow(2, k) !== this.bufferSize) { throw "buffer size must be a power of 2."; } | |
if (this.bufferSize !== _buffer.length) { throw "buffer is not the same size as defined FFT. FFT: " + bufferSize + " buffer: " + buffer.length; } | |
var halfSize = 1; | |
var phaseShiftStepReal, phaseShiftStepImag; | |
var currentPhaseShiftReal, currentPhaseShiftImag; | |
var tr, ti; | |
var tmpReal; | |
var i, o; | |
// STEP 1 - fill up the 'real' array with the signal according to the reverseTable ordering | |
// makes it faster for memory access later as it's already copied in there and we can adjustable | |
// iterate through it. | |
for (i = 0; i < this.bufferSize; i++) { | |
this.real[i] = _buffer[this.reverseTable[i]]; | |
this.imag[i] = 0; | |
} | |
// STEP 2 - do the actual discrete fourier transform (DFT). | |
// halfSize will increment in powers of two up to half of the FFT size (nyquist) | |
// so for a 1024-sample FFT, we're doing 10 outer loops (1, 2, 4, 8, 16, 32, 64, 128, 256, 512). | |
while (halfSize < this.bufferSize) { | |
// figure out the phase increment necessary for each sample size | |
phaseShiftStepReal = this.cosTable[halfSize]; | |
phaseShiftStepImag = this.sinTable[halfSize]; | |
// starting x,y positions | |
currentPhaseShiftReal = 1; | |
currentPhaseShiftImag = 0; | |
// intermediate loop - fftStep will increment from 0 to halfSize-1, | |
// i.e. number of fftStep loops equals to halfSize each time. | |
// each one of these passes is a DFT. | |
for (var fftStep = 0; fftStep < halfSize; fftStep++) { | |
i = fftStep; | |
// inner loop - i and o will integrate the convolution of the input signal | |
// with cosine and sine functions at a period equal to the fftStep | |
while (i < this.bufferSize) { | |
o = i + halfSize; | |
// uncomment the line below to see what's going on: | |
// console.log('halfSize : ' + halfSize + ', fftStep : ' + fftStep + ', i : ' + i + ', o : ' + o + ', psr : ' + currentPhaseShiftReal + ', psi : ' + currentPhaseShiftImag); | |
tr = (currentPhaseShiftReal * this.real[o]) - (currentPhaseShiftImag * this.imag[o]); | |
ti = (currentPhaseShiftReal * this.imag[o]) + (currentPhaseShiftImag * this.real[o]); | |
this.real[o] = this.real[i] - tr; | |
this.imag[o] = this.imag[i] - ti; | |
this.real[i] += tr; | |
this.imag[i] += ti; | |
i += halfSize << 1; | |
} | |
// increment the sampling interval for the next DFT in the loop: | |
tmpReal = currentPhaseShiftReal; | |
currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); | |
currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); | |
} | |
// shift up halfSize for next outer loop | |
halfSize = halfSize << 1; | |
} | |
if(this.doSpectrum) this.calculateSpectrum(); // calculate mag/phase from real/imag | |
} | |
// performs an inverse FFT transform (an IFFT) on either real/imag | |
// data passed into the function or, if called without arguments, | |
// the current spectral frame stored in the object. | |
// this converts the data from the frequency domain into the time domain. | |
// the function returns a buffer containing the time-domain signal. | |
p5.FastFourierTransform.prototype.inverse = function(_real, _imag) { | |
_real = _real || this.real; | |
_imag = _imag || this.imag; | |
var halfSize = 1; | |
var phaseShiftStepReal, phaseShiftStepImag; | |
var currentPhaseShiftReal, currentPhaseShiftImag; | |
var off; | |
var tr, ti; | |
var tmpReal; | |
var i; | |
for (i = 0; i < this.bufferSize; i++) { | |
_imag[i] *= -1; | |
} | |
var revReal = new Float64Array(this.bufferSize); | |
var revImag = new Float64Array(this.bufferSize); | |
for (i = 0; i < _real.length; i++) { | |
revReal[i] = _real[this.reverseTable[i]]; | |
revImag[i] = _imag[this.reverseTable[i]]; | |
} | |
_real = revReal; | |
_imag = revImag; | |
while (halfSize < this.bufferSize) { | |
phaseShiftStepReal = this.cosTable[halfSize]; | |
phaseShiftStepImag = this.sinTable[halfSize]; | |
currentPhaseShiftReal = 1; | |
currentPhaseShiftImag = 0; | |
for (var fftStep = 0; fftStep < halfSize; fftStep++) { | |
i = fftStep; | |
while (i < this.bufferSize) { | |
off = i + halfSize; | |
tr = (currentPhaseShiftReal * _real[off]) - (currentPhaseShiftImag * _imag[off]); | |
ti = (currentPhaseShiftReal * _imag[off]) + (currentPhaseShiftImag * _real[off]); | |
_real[off] = _real[i] - tr; | |
_imag[off] = _imag[i] - ti; | |
_real[i] += tr; | |
_imag[i] += ti; | |
i += halfSize << 1; | |
} | |
tmpReal = currentPhaseShiftReal; | |
currentPhaseShiftReal = (tmpReal * phaseShiftStepReal) - (currentPhaseShiftImag * phaseShiftStepImag); | |
currentPhaseShiftImag = (tmpReal * phaseShiftStepImag) + (currentPhaseShiftImag * phaseShiftStepReal); | |
} | |
halfSize = halfSize << 1; | |
} | |
var buffer = new Float64Array(this.bufferSize); // this should be reused instead | |
for (i = 0; i < this.bufferSize; i++) { | |
buffer[i] = _real[i] / this.bufferSize; | |
} | |
return buffer; | |
} | |
// ============================================================================= | |
// Luke's Misc. Utilities (extends p5.js) | |
// ============================================================================= | |
// constrained integer mapping function (useful for array lookup). | |
// similar to the Max [zmap] object when used with integers. | |
// syntactically equivalent to the p5.js / processing map() function. | |
p5.prototype.imap = function(_x, _a, _b, _c, _d) { | |
return(constrain(floor(map(_x, _a, _b, _c, _d)), min(_c,_d), max(_c, _d)-1)); | |
} | |
// number wrapping (courtesy of @pd-l2ork puredata [pong]) | |
p5.prototype.wrap = function(_x, _min, _max) { | |
_a = min(_min, _max); | |
_b = max(_min, _max); | |
var _y; | |
var _r = _b-_a; // range | |
if(_x < _b && _x >= _a) { // normal | |
return(_x); | |
} | |
else if(_a==_b) { // catch | |
return(_a); | |
} | |
else { | |
if(_x < _a) { | |
_y = _x; | |
while(_y < _a){ | |
_y += _r; | |
}; | |
} | |
else { | |
_y = ((_x-_a)%_r) + _a; | |
} | |
} | |
return(_y); | |
} | |
// number folding (courtesy of @pd-l2ork puredata [pong]) | |
p5.prototype.fold = function(_x, _min, _max) { | |
_a = min(_min, _max); | |
_b = max(_min, _max); | |
var _y; | |
var _r = _b-_a; // range | |
if(_x < _b && _x >= _a) { // normal | |
return(_x); | |
} | |
else if(_a==_b) { // catch | |
return(_a); | |
} | |
else { | |
if(_x < _a) { | |
var _d = _a - _x; // diff between input and minimum (positive) | |
var _m = floor(_d/_r); // case where input is more than a range away from minval | |
if(_m % 2 == 0) { // even number of ranges away = counting up from min | |
_d = _d - _m*_r; | |
_y = _d + _a; | |
} | |
else { // odd number of ranges away = counting down from max | |
_d = _d - _m*_r; | |
_y = _b - _d; | |
} | |
} | |
else { // input > maxval | |
var _d = _x - _b; // diff between input and max (positive) | |
var _m = floor(_d/_r); // case where input is more than a range away from maxval | |
if(_m % 2 == 0) { // even number of ranges away = counting down from max | |
_d = _d - _m*_r; | |
_y = _b - _d; | |
} | |
else { //odd number of ranges away = counting up from min | |
_d = _d - _m*_r; | |
_y = _d + _a; | |
} | |
} | |
} | |
return(_y); | |
} | |
// create n-dimensional arrays | |
p5.prototype.createArray = function(_len) | |
{ | |
var _arr = new Array(_len || 0).fill(0), | |
i = _len; | |
if (arguments.length > 1) { | |
var args = Array.prototype.slice.call(arguments, 1); | |
while(i--) _arr[_len-1 - i] = this.createArray.apply(this, args); | |
} | |
return _arr; | |
} | |
// normalize a numerical array to absmax=1.0 without shifting DC | |
p5.prototype.normalizeArray = function(_array) { | |
var _max = max(max(_array), abs(min(_array))); | |
return(_array.map(function(_v) { return _v/_max; })); | |
} | |
// resize a numerical array to a new size with linear interpolation | |
p5.prototype.resizeArray = function(_array, _newlen) { | |
var _out = []; | |
for(var i = 0;i<_newlen;i++) | |
{ | |
var aptr = map(i, 0, _newlen-1, 0, _array.length-1); | |
var a = floor(aptr); | |
var b = ceil(aptr); | |
var l = aptr%1.0; | |
_out[i] = lerp(_array[a], _array[b], l); | |
} | |
return(_out); | |
} | |
// multiply two arrays | |
p5.prototype.multiplyArray = function(_a1, _a2) { | |
if(!Array.isArray(_a2)) _a2 = [_a2]; // allow scalars | |
var _out = []; | |
if(_a1.length!=_a2.length) _a2 = resizeArray(_a2, _a1.length); | |
for(var i = 0;i<_a1.length;i++) | |
{ | |
_out[i] = _a1[i] * _a2[i]; | |
} | |
return(_out); | |
} | |
// add two arrays | |
p5.prototype.addArray = function(_a1, _a2) { | |
if(!Array.isArray(_a2)) _a2 = [_a2]; // allow scalars | |
var _out = []; | |
if(_a1.length!=_a2.length) _a2 = resizeArray(_a2, _a1.length); | |
for(var i = 0;i<_a1.length;i++) | |
{ | |
_out[i] = _a1[i] + _a2[i]; | |
} | |
return(_out); | |
} | |
// subtract two arrays | |
p5.prototype.subtractArray = function(_a1, _a2) { | |
if(!Array.isArray(_a2)) _a2 = [_a2]; // allow scalars | |
var _out = []; | |
if(_a1.length!=_a2.length) _a2 = resizeArray(_a2, _a1.length); | |
for(var i = 0;i<_a1.length;i++) | |
{ | |
_out[i] = _a1[i] - _a2[i]; | |
} | |
return(_out); | |
} | |
// divide two arrays | |
p5.prototype.divideArray = function(_a1, _a2) { | |
if(!Array.isArray(_a2)) _a2 = [_a2]; // allow scalars | |
var _out = []; | |
if(_a1.length!=_a2.length) _a2 = resizeArray(_a2, _a1.length); | |
for(var i = 0;i<_a1.length;i++) | |
{ | |
_out[i] = _a1[i] / _a2[i]; | |
} | |
return(_out); | |
} | |
// modulo two arrays | |
p5.prototype.moduloArray = function(_a1, _a2) { | |
if(!Array.isArray(_a2)) _a2 = [_a2]; // allow scalars | |
var _out = []; | |
if(_a1.length!=_a2.length) _a2 = resizeArray(_a2, _a1.length); | |
for(var i = 0;i<_a1.length;i++) | |
{ | |
_out[i] = _a1[i] % _a2[i]; | |
} | |
return(_out); | |
} | |
// return the sum of an array | |
p5.prototype.sumArray = function(_a) { | |
var _s = _a.reduce(function(_acc, _val) { | |
return(_acc+_val); | |
}); | |
return(_s); | |
} | |
// Java Float.floatToIntBits() IEEE 754 / 32-bit (h/t @mattdesl): | |
p5.prototype.f2ib = function(_x) | |
{ | |
var int8 = new Int8Array(4); | |
var int32 = new Int32Array(int8.buffer, 0, 1); | |
var float32 = new Float32Array(int8.buffer, 0, 1); | |
float32[0] = _x; | |
return(int32[0]); | |
} | |
// Java Float.intBitstoFloat() IEEE 754 / 32-bit (h/t @mattdesl): | |
p5.prototype.ib2f = function(_x) | |
{ | |
var int8 = new Int8Array(4); | |
var int32 = new Int32Array(int8.buffer, 0, 1); | |
var float32 = new Float32Array(int8.buffer, 0, 1); | |
int32[0] = _x; | |
return(float32[0]); | |
} | |
// normalized sinc function (integral equals 1, not PI) | |
// the normalized sinc is the FT of the rectangular function. | |
// 'sinc' is short for sinus cardinalis (woodward '52): | |
// http://www.norbertwiener.umd.edu/crowds/documents/Woodward52.pdf | |
p5.prototype.sinc = function(_x) { | |
return(sin(PI*_x)/(PI*_x)); | |
} | |
// besselI0 - regular modified cylindrical Bessel function (Bessel I) | |
// https://en.wikipedia.org/wiki/Bessel_function | |
// ported from kbdwindow.cpp by craig stuart sapp @ CCRMA ca. 2001 | |
p5.prototype.besselI0 = function(_x) { | |
var denominator; | |
var numerator; | |
var z; | |
if (_x == 0.0) { | |
return(1.0); | |
} else { | |
z = _x * _x; | |
numerator = (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* (z* | |
(z* 0.210580722890567e-22 + 0.380715242345326e-19 ) + | |
0.479440257548300e-16) + 0.435125971262668e-13 ) + | |
0.300931127112960e-10) + 0.160224679395361e-7 ) + | |
0.654858370096785e-5) + 0.202591084143397e-2 ) + | |
0.463076284721000e0) + 0.754337328948189e2 ) + | |
0.830792541809429e4) + 0.571661130563785e6 ) + | |
0.216415572361227e8) + 0.356644482244025e9 ) + | |
0.144048298227235e10); | |
denominator = (z*(z*(z-0.307646912682801e4)+ | |
0.347626332405882e7)-0.144048298227235e10); | |
} | |
return(-numerator/denominator); | |
} | |
// plot an array to the console as if it were a VT100 terminal. | |
// ported from a copy of fplot.c found on luke's NeXTstation. | |
// fplot.c seems rewritten from FORTRAN, presumably by | |
// paul lansky, while porting MIX to C in 83/84. | |
// man page last troff'ed january 31, 1987, 6:56PM by paul lansky. | |
// c code last modified october 18, 1990, 2:26PM by brad garton. | |
p5.prototype.fplot = function(_array, _css) { | |
var a; | |
if(!Array.isArray(_array)) a = [_array]; // single value | |
if(Array.isArray(_array)) a = _array; // normal array | |
if(_array.constructor == Float32Array) a = Array.prototype.slice.call(_array); | |
if(_array.constructor == Float64Array) a = Array.prototype.slice.call(_array); | |
var TERMWIDTH = 80; // columns (1 additional will be used for left margin) | |
var TERMHEIGHT = 23; // VT100 is 24 rows ('take back one kadam...') | |
var out = []; | |
var si, phase; | |
var i, j, k, len, wave; | |
var line = ''; | |
len = _array.length; | |
// decimate or interpolate array to TERMWIDTH values, scale to absmax=1. | |
out = resizeArray(a, TERMWIDTH); | |
out = normalizeArray(out); | |
// plot the fucker | |
si = 1./((TERMHEIGHT-1)/2.); | |
phase = 1.; | |
for(i = 0; i < TERMHEIGHT; i++) { | |
k = 0; | |
// add alternator to left margin of line - prevents browser consoles | |
// from interpreting duplicate lines and only printing them once: | |
if(i%2) line= '~'; else line='|'; | |
// format line based on function being within phase boundary | |
for(j = 0; j < TERMWIDTH-1; j++) { | |
if(isNaN(out[j])) out[j] = 0; // filter out garbage | |
if((out[j]>=phase) && (out[j]<phase+si)) { | |
line+= (out[j+1] > phase+si) ? '/' : '-'; | |
if(out[j+1] < phase) line+= '\\'; | |
k = j; | |
} | |
else if (((out[j]<phase)&&(out[j+1]>phase+si)) || | |
((out[j]>phase+si)&&(out[j+1]<phase))) { | |
line+= '|'; | |
k = j; | |
} | |
else line+= ' '; // function isn't within range... fill with space | |
} | |
// center line | |
if ((0>=phase) && (0<phase+si)) { | |
line = '~'; // alternator | |
for(j = 0; j < TERMWIDTH; j++) line+= '-'; | |
k = TERMWIDTH-2; | |
} | |
console.log('%c'+line, _css); // print, including CSS | |
phase-= si; // decrement phase | |
} | |
return(0); | |
} | |
})); | |
/* | |
todo: | |
*/ | |
// EOF |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"commits": [ | |
{ | |
"sha": "ed709d147c0cebad940fc8e0783cb573544ab11e", | |
"name": "xinze's example (random phase)" | |
}, | |
{ | |
"sha": "99643c952c26fc6deeff46770740e4887f98f004", | |
"name": "side scroller" | |
}, | |
{ | |
"sha": "99643c952c26fc6deeff46770740e4887f98f004", | |
"name": "scrolling forward" | |
}, | |
{ | |
"sha": "1e2a6ee87fb38a4cd5b7b77c409b9e8655b7fdc8", | |
"name": "george's example" | |
}, | |
{ | |
"sha": "9d0fd46c752c566ae896f0c8110291faeefab0f5", | |
"name": "second noise movement" | |
}, | |
{ | |
"sha": "90f882383fc3f2dff439b0366d43bb781d9866d4", | |
"name": "noise movement" | |
}, | |
{ | |
"sha": "e9fb0f47f45ec2b646ba566a9aa458f434e00abd", | |
"name": "noise example" | |
}, | |
{ | |
"sha": "251aa210716e3e9020b728b125b29eafb052aca3", | |
"name": "mouse debugging" | |
}, | |
{ | |
"sha": "7e8124570ddbb3c160b1ac006f9999658bff9399", | |
"name": "adding easing to animation" | |
}, | |
{ | |
"sha": "f378953ffe76ac932903fa8bbb74349e8d1ba882", | |
"name": "A vs B easing functions" | |
}, | |
{ | |
"sha": "cb479118411cb0fa385f808aee52998c77f62bfe", | |
"name": "easing functions" | |
}, | |
{ | |
"sha": "64c02aed908cb8db8a516724ca0dd15f9ea63b82", | |
"name": "blue tilt example" | |
}, | |
{ | |
"sha": "ff0515e3275176e3eff5a8b3f595f0a6e2b4358f", | |
"name": "adding motion blur" | |
}, | |
{ | |
"sha": "c85505dcf3def61038b29c8ec9a449c827460271", | |
"name": "grid flowing loop" | |
}, | |
{ | |
"sha": "32d8814c75f4ca88e2df517ccd013c85c65c37a3", | |
"name": "grid animation loop" | |
}, | |
{ | |
"sha": "18a6bcc7fd4db27f59effb35ff5a2eea5c88eafd", | |
"name": "first animation loop" | |
}, | |
{ | |
"sha": "b85d47122ed4e5540c4deb720337ef27eede41b6", | |
"name": "final layering example" | |
}, | |
{ | |
"sha": "861f45ff34642b82c006e6bc992bb94b8ed4e559", | |
"name": "ordering example" | |
}, | |
{ | |
"sha": "65f23227f06f5cbf9ec740f7533c2cfa7b17d660", | |
"name": "second grid example" | |
}, | |
{ | |
"sha": "e31156e30f816889b733e9829fdd6ef7e6d8bf99", | |
"name": "first grid example" | |
}, | |
{ | |
"sha": "ec0388ff73409b9dd420b2a439a700969133151f", | |
"name": "inital sketch (red micky)" | |
} | |
] | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* A fast javascript implementation of simplex noise by Jonas Wagner | |
Based on a speed-improved simplex noise algorithm for 2D, 3D and 4D in Java. | |
Which is based on example code by Stefan Gustavson ([email protected]). | |
With Optimisations by Peter Eastman ([email protected]). | |
Better rank ordering method by Stefan Gustavson in 2012. | |
Copyright (c) 2018 Jonas Wagner | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. | |
*/ | |
(function() { | |
'use strict'; | |
var F2 = 0.5 * (Math.sqrt(3.0) - 1.0); | |
var G2 = (3.0 - Math.sqrt(3.0)) / 6.0; | |
var F3 = 1.0 / 3.0; | |
var G3 = 1.0 / 6.0; | |
var F4 = (Math.sqrt(5.0) - 1.0) / 4.0; | |
var G4 = (5.0 - Math.sqrt(5.0)) / 20.0; | |
function SimplexNoise(randomOrSeed) { | |
var random; | |
if (typeof randomOrSeed == 'function') { | |
random = randomOrSeed; | |
} | |
else if (randomOrSeed) { | |
random = alea(randomOrSeed); | |
} else { | |
random = Math.random; | |
} | |
this.p = buildPermutationTable(random); | |
this.perm = new Uint8Array(512); | |
this.permMod12 = new Uint8Array(512); | |
for (var i = 0; i < 512; i++) { | |
this.perm[i] = this.p[i & 255]; | |
this.permMod12[i] = this.perm[i] % 12; | |
} | |
} | |
SimplexNoise.prototype = { | |
grad3: new Float32Array([1, 1, 0, | |
-1, 1, 0, | |
1, -1, 0, | |
-1, -1, 0, | |
1, 0, 1, | |
-1, 0, 1, | |
1, 0, -1, | |
-1, 0, -1, | |
0, 1, 1, | |
0, -1, 1, | |
0, 1, -1, | |
0, -1, -1]), | |
grad4: new Float32Array([0, 1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, | |
0, -1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, | |
1, 0, 1, 1, 1, 0, 1, -1, 1, 0, -1, 1, 1, 0, -1, -1, | |
-1, 0, 1, 1, -1, 0, 1, -1, -1, 0, -1, 1, -1, 0, -1, -1, | |
1, 1, 0, 1, 1, 1, 0, -1, 1, -1, 0, 1, 1, -1, 0, -1, | |
-1, 1, 0, 1, -1, 1, 0, -1, -1, -1, 0, 1, -1, -1, 0, -1, | |
1, 1, 1, 0, 1, 1, -1, 0, 1, -1, 1, 0, 1, -1, -1, 0, | |
-1, 1, 1, 0, -1, 1, -1, 0, -1, -1, 1, 0, -1, -1, -1, 0]), | |
noise2D: function(xin, yin) { | |
var permMod12 = this.permMod12; | |
var perm = this.perm; | |
var grad3 = this.grad3; | |
var n0 = 0; // Noise contributions from the three corners | |
var n1 = 0; | |
var n2 = 0; | |
// Skew the input space to determine which simplex cell we're in | |
var s = (xin + yin) * F2; // Hairy factor for 2D | |
var i = Math.floor(xin + s); | |
var j = Math.floor(yin + s); | |
var t = (i + j) * G2; | |
var X0 = i - t; // Unskew the cell origin back to (x,y) space | |
var Y0 = j - t; | |
var x0 = xin - X0; // The x,y distances from the cell origin | |
var y0 = yin - Y0; | |
// For the 2D case, the simplex shape is an equilateral triangle. | |
// Determine which simplex we are in. | |
var i1, j1; // Offsets for second (middle) corner of simplex in (i,j) coords | |
if (x0 > y0) { | |
i1 = 1; | |
j1 = 0; | |
} // lower triangle, XY order: (0,0)->(1,0)->(1,1) | |
else { | |
i1 = 0; | |
j1 = 1; | |
} // upper triangle, YX order: (0,0)->(0,1)->(1,1) | |
// A step of (1,0) in (i,j) means a step of (1-c,-c) in (x,y), and | |
// a step of (0,1) in (i,j) means a step of (-c,1-c) in (x,y), where | |
// c = (3-sqrt(3))/6 | |
var x1 = x0 - i1 + G2; // Offsets for middle corner in (x,y) unskewed coords | |
var y1 = y0 - j1 + G2; | |
var x2 = x0 - 1.0 + 2.0 * G2; // Offsets for last corner in (x,y) unskewed coords | |
var y2 = y0 - 1.0 + 2.0 * G2; | |
// Work out the hashed gradient indices of the three simplex corners | |
var ii = i & 255; | |
var jj = j & 255; | |
// Calculate the contribution from the three corners | |
var t0 = 0.5 - x0 * x0 - y0 * y0; | |
if (t0 >= 0) { | |
var gi0 = permMod12[ii + perm[jj]] * 3; | |
t0 *= t0; | |
n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0); // (x,y) of grad3 used for 2D gradient | |
} | |
var t1 = 0.5 - x1 * x1 - y1 * y1; | |
if (t1 >= 0) { | |
var gi1 = permMod12[ii + i1 + perm[jj + j1]] * 3; | |
t1 *= t1; | |
n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1); | |
} | |
var t2 = 0.5 - x2 * x2 - y2 * y2; | |
if (t2 >= 0) { | |
var gi2 = permMod12[ii + 1 + perm[jj + 1]] * 3; | |
t2 *= t2; | |
n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2); | |
} | |
// Add contributions from each corner to get the final noise value. | |
// The result is scaled to return values in the interval [-1,1]. | |
return 70.0 * (n0 + n1 + n2); | |
}, | |
// 3D simplex noise | |
noise3D: function(xin, yin, zin) { | |
var permMod12 = this.permMod12; | |
var perm = this.perm; | |
var grad3 = this.grad3; | |
var n0, n1, n2, n3; // Noise contributions from the four corners | |
// Skew the input space to determine which simplex cell we're in | |
var s = (xin + yin + zin) * F3; // Very nice and simple skew factor for 3D | |
var i = Math.floor(xin + s); | |
var j = Math.floor(yin + s); | |
var k = Math.floor(zin + s); | |
var t = (i + j + k) * G3; | |
var X0 = i - t; // Unskew the cell origin back to (x,y,z) space | |
var Y0 = j - t; | |
var Z0 = k - t; | |
var x0 = xin - X0; // The x,y,z distances from the cell origin | |
var y0 = yin - Y0; | |
var z0 = zin - Z0; | |
// For the 3D case, the simplex shape is a slightly irregular tetrahedron. | |
// Determine which simplex we are in. | |
var i1, j1, k1; // Offsets for second corner of simplex in (i,j,k) coords | |
var i2, j2, k2; // Offsets for third corner of simplex in (i,j,k) coords | |
if (x0 >= y0) { | |
if (y0 >= z0) { | |
i1 = 1; | |
j1 = 0; | |
k1 = 0; | |
i2 = 1; | |
j2 = 1; | |
k2 = 0; | |
} // X Y Z order | |
else if (x0 >= z0) { | |
i1 = 1; | |
j1 = 0; | |
k1 = 0; | |
i2 = 1; | |
j2 = 0; | |
k2 = 1; | |
} // X Z Y order | |
else { | |
i1 = 0; | |
j1 = 0; | |
k1 = 1; | |
i2 = 1; | |
j2 = 0; | |
k2 = 1; | |
} // Z X Y order | |
} | |
else { // x0<y0 | |
if (y0 < z0) { | |
i1 = 0; | |
j1 = 0; | |
k1 = 1; | |
i2 = 0; | |
j2 = 1; | |
k2 = 1; | |
} // Z Y X order | |
else if (x0 < z0) { | |
i1 = 0; | |
j1 = 1; | |
k1 = 0; | |
i2 = 0; | |
j2 = 1; | |
k2 = 1; | |
} // Y Z X order | |
else { | |
i1 = 0; | |
j1 = 1; | |
k1 = 0; | |
i2 = 1; | |
j2 = 1; | |
k2 = 0; | |
} // Y X Z order | |
} | |
// A step of (1,0,0) in (i,j,k) means a step of (1-c,-c,-c) in (x,y,z), | |
// a step of (0,1,0) in (i,j,k) means a step of (-c,1-c,-c) in (x,y,z), and | |
// a step of (0,0,1) in (i,j,k) means a step of (-c,-c,1-c) in (x,y,z), where | |
// c = 1/6. | |
var x1 = x0 - i1 + G3; // Offsets for second corner in (x,y,z) coords | |
var y1 = y0 - j1 + G3; | |
var z1 = z0 - k1 + G3; | |
var x2 = x0 - i2 + 2.0 * G3; // Offsets for third corner in (x,y,z) coords | |
var y2 = y0 - j2 + 2.0 * G3; | |
var z2 = z0 - k2 + 2.0 * G3; | |
var x3 = x0 - 1.0 + 3.0 * G3; // Offsets for last corner in (x,y,z) coords | |
var y3 = y0 - 1.0 + 3.0 * G3; | |
var z3 = z0 - 1.0 + 3.0 * G3; | |
// Work out the hashed gradient indices of the four simplex corners | |
var ii = i & 255; | |
var jj = j & 255; | |
var kk = k & 255; | |
// Calculate the contribution from the four corners | |
var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0; | |
if (t0 < 0) n0 = 0.0; | |
else { | |
var gi0 = permMod12[ii + perm[jj + perm[kk]]] * 3; | |
t0 *= t0; | |
n0 = t0 * t0 * (grad3[gi0] * x0 + grad3[gi0 + 1] * y0 + grad3[gi0 + 2] * z0); | |
} | |
var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1; | |
if (t1 < 0) n1 = 0.0; | |
else { | |
var gi1 = permMod12[ii + i1 + perm[jj + j1 + perm[kk + k1]]] * 3; | |
t1 *= t1; | |
n1 = t1 * t1 * (grad3[gi1] * x1 + grad3[gi1 + 1] * y1 + grad3[gi1 + 2] * z1); | |
} | |
var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2; | |
if (t2 < 0) n2 = 0.0; | |
else { | |
var gi2 = permMod12[ii + i2 + perm[jj + j2 + perm[kk + k2]]] * 3; | |
t2 *= t2; | |
n2 = t2 * t2 * (grad3[gi2] * x2 + grad3[gi2 + 1] * y2 + grad3[gi2 + 2] * z2); | |
} | |
var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3; | |
if (t3 < 0) n3 = 0.0; | |
else { | |
var gi3 = permMod12[ii + 1 + perm[jj + 1 + perm[kk + 1]]] * 3; | |
t3 *= t3; | |
n3 = t3 * t3 * (grad3[gi3] * x3 + grad3[gi3 + 1] * y3 + grad3[gi3 + 2] * z3); | |
} | |
// Add contributions from each corner to get the final noise value. | |
// The result is scaled to stay just inside [-1,1] | |
return 32.0 * (n0 + n1 + n2 + n3); | |
}, | |
// 4D simplex noise, better simplex rank ordering method 2012-03-09 | |
noise4D: function(x, y, z, w) { | |
var perm = this.perm; | |
var grad4 = this.grad4; | |
var n0, n1, n2, n3, n4; // Noise contributions from the five corners | |
// Skew the (x,y,z,w) space to determine which cell of 24 simplices we're in | |
var s = (x + y + z + w) * F4; // Factor for 4D skewing | |
var i = Math.floor(x + s); | |
var j = Math.floor(y + s); | |
var k = Math.floor(z + s); | |
var l = Math.floor(w + s); | |
var t = (i + j + k + l) * G4; // Factor for 4D unskewing | |
var X0 = i - t; // Unskew the cell origin back to (x,y,z,w) space | |
var Y0 = j - t; | |
var Z0 = k - t; | |
var W0 = l - t; | |
var x0 = x - X0; // The x,y,z,w distances from the cell origin | |
var y0 = y - Y0; | |
var z0 = z - Z0; | |
var w0 = w - W0; | |
// For the 4D case, the simplex is a 4D shape I won't even try to describe. | |
// To find out which of the 24 possible simplices we're in, we need to | |
// determine the magnitude ordering of x0, y0, z0 and w0. | |
// Six pair-wise comparisons are performed between each possible pair | |
// of the four coordinates, and the results are used to rank the numbers. | |
var rankx = 0; | |
var ranky = 0; | |
var rankz = 0; | |
var rankw = 0; | |
if (x0 > y0) rankx++; | |
else ranky++; | |
if (x0 > z0) rankx++; | |
else rankz++; | |
if (x0 > w0) rankx++; | |
else rankw++; | |
if (y0 > z0) ranky++; | |
else rankz++; | |
if (y0 > w0) ranky++; | |
else rankw++; | |
if (z0 > w0) rankz++; | |
else rankw++; | |
var i1, j1, k1, l1; // The integer offsets for the second simplex corner | |
var i2, j2, k2, l2; // The integer offsets for the third simplex corner | |
var i3, j3, k3, l3; // The integer offsets for the fourth simplex corner | |
// simplex[c] is a 4-vector with the numbers 0, 1, 2 and 3 in some order. | |
// Many values of c will never occur, since e.g. x>y>z>w makes x<z, y<w and x<w | |
// impossible. Only the 24 indices which have non-zero entries make any sense. | |
// We use a thresholding to set the coordinates in turn from the largest magnitude. | |
// Rank 3 denotes the largest coordinate. | |
i1 = rankx >= 3 ? 1 : 0; | |
j1 = ranky >= 3 ? 1 : 0; | |
k1 = rankz >= 3 ? 1 : 0; | |
l1 = rankw >= 3 ? 1 : 0; | |
// Rank 2 denotes the second largest coordinate. | |
i2 = rankx >= 2 ? 1 : 0; | |
j2 = ranky >= 2 ? 1 : 0; | |
k2 = rankz >= 2 ? 1 : 0; | |
l2 = rankw >= 2 ? 1 : 0; | |
// Rank 1 denotes the second smallest coordinate. | |
i3 = rankx >= 1 ? 1 : 0; | |
j3 = ranky >= 1 ? 1 : 0; | |
k3 = rankz >= 1 ? 1 : 0; | |
l3 = rankw >= 1 ? 1 : 0; | |
// The fifth corner has all coordinate offsets = 1, so no need to compute that. | |
var x1 = x0 - i1 + G4; // Offsets for second corner in (x,y,z,w) coords | |
var y1 = y0 - j1 + G4; | |
var z1 = z0 - k1 + G4; | |
var w1 = w0 - l1 + G4; | |
var x2 = x0 - i2 + 2.0 * G4; // Offsets for third corner in (x,y,z,w) coords | |
var y2 = y0 - j2 + 2.0 * G4; | |
var z2 = z0 - k2 + 2.0 * G4; | |
var w2 = w0 - l2 + 2.0 * G4; | |
var x3 = x0 - i3 + 3.0 * G4; // Offsets for fourth corner in (x,y,z,w) coords | |
var y3 = y0 - j3 + 3.0 * G4; | |
var z3 = z0 - k3 + 3.0 * G4; | |
var w3 = w0 - l3 + 3.0 * G4; | |
var x4 = x0 - 1.0 + 4.0 * G4; // Offsets for last corner in (x,y,z,w) coords | |
var y4 = y0 - 1.0 + 4.0 * G4; | |
var z4 = z0 - 1.0 + 4.0 * G4; | |
var w4 = w0 - 1.0 + 4.0 * G4; | |
// Work out the hashed gradient indices of the five simplex corners | |
var ii = i & 255; | |
var jj = j & 255; | |
var kk = k & 255; | |
var ll = l & 255; | |
// Calculate the contribution from the five corners | |
var t0 = 0.6 - x0 * x0 - y0 * y0 - z0 * z0 - w0 * w0; | |
if (t0 < 0) n0 = 0.0; | |
else { | |
var gi0 = (perm[ii + perm[jj + perm[kk + perm[ll]]]] % 32) * 4; | |
t0 *= t0; | |
n0 = t0 * t0 * (grad4[gi0] * x0 + grad4[gi0 + 1] * y0 + grad4[gi0 + 2] * z0 + grad4[gi0 + 3] * w0); | |
} | |
var t1 = 0.6 - x1 * x1 - y1 * y1 - z1 * z1 - w1 * w1; | |
if (t1 < 0) n1 = 0.0; | |
else { | |
var gi1 = (perm[ii + i1 + perm[jj + j1 + perm[kk + k1 + perm[ll + l1]]]] % 32) * 4; | |
t1 *= t1; | |
n1 = t1 * t1 * (grad4[gi1] * x1 + grad4[gi1 + 1] * y1 + grad4[gi1 + 2] * z1 + grad4[gi1 + 3] * w1); | |
} | |
var t2 = 0.6 - x2 * x2 - y2 * y2 - z2 * z2 - w2 * w2; | |
if (t2 < 0) n2 = 0.0; | |
else { | |
var gi2 = (perm[ii + i2 + perm[jj + j2 + perm[kk + k2 + perm[ll + l2]]]] % 32) * 4; | |
t2 *= t2; | |
n2 = t2 * t2 * (grad4[gi2] * x2 + grad4[gi2 + 1] * y2 + grad4[gi2 + 2] * z2 + grad4[gi2 + 3] * w2); | |
} | |
var t3 = 0.6 - x3 * x3 - y3 * y3 - z3 * z3 - w3 * w3; | |
if (t3 < 0) n3 = 0.0; | |
else { | |
var gi3 = (perm[ii + i3 + perm[jj + j3 + perm[kk + k3 + perm[ll + l3]]]] % 32) * 4; | |
t3 *= t3; | |
n3 = t3 * t3 * (grad4[gi3] * x3 + grad4[gi3 + 1] * y3 + grad4[gi3 + 2] * z3 + grad4[gi3 + 3] * w3); | |
} | |
var t4 = 0.6 - x4 * x4 - y4 * y4 - z4 * z4 - w4 * w4; | |
if (t4 < 0) n4 = 0.0; | |
else { | |
var gi4 = (perm[ii + 1 + perm[jj + 1 + perm[kk + 1 + perm[ll + 1]]]] % 32) * 4; | |
t4 *= t4; | |
n4 = t4 * t4 * (grad4[gi4] * x4 + grad4[gi4 + 1] * y4 + grad4[gi4 + 2] * z4 + grad4[gi4 + 3] * w4); | |
} | |
// Sum up and scale the result to cover the range [-1,1] | |
return 27.0 * (n0 + n1 + n2 + n3 + n4); | |
} | |
}; | |
function buildPermutationTable(random) { | |
var i; | |
var p = new Uint8Array(256); | |
for (i = 0; i < 256; i++) { | |
p[i] = i; | |
} | |
for (i = 0; i < 255; i++) { | |
var r = i + ~~(random() * (256 - i)); | |
var aux = p[i]; | |
p[i] = p[r]; | |
p[r] = aux; | |
} | |
return p; | |
} | |
SimplexNoise._buildPermutationTable = buildPermutationTable; | |
/* | |
The ALEA PRNG and masher code used by simplex-noise.js | |
is based on code by Johannes Baagøe, modified by Jonas Wagner. | |
See alea.md for the full license. | |
*/ | |
function alea() { | |
var s0 = 0; | |
var s1 = 0; | |
var s2 = 0; | |
var c = 1; | |
var mash = masher(); | |
s0 = mash(' '); | |
s1 = mash(' '); | |
s2 = mash(' '); | |
for (var i = 0; i < arguments.length; i++) { | |
s0 -= mash(arguments[i]); | |
if (s0 < 0) { | |
s0 += 1; | |
} | |
s1 -= mash(arguments[i]); | |
if (s1 < 0) { | |
s1 += 1; | |
} | |
s2 -= mash(arguments[i]); | |
if (s2 < 0) { | |
s2 += 1; | |
} | |
} | |
mash = null; | |
return function() { | |
var t = 2091639 * s0 + c * 2.3283064365386963e-10; // 2^-32 | |
s0 = s1; | |
s1 = s2; | |
return s2 = t - (c = t | 0); | |
}; | |
} | |
function masher() { | |
var n = 0xefc8249d; | |
return function(data) { | |
data = data.toString(); | |
for (var i = 0; i < data.length; i++) { | |
n += data.charCodeAt(i); | |
var h = 0.02519603282416938 * n; | |
n = h >>> 0; | |
h -= n; | |
h *= n; | |
n = h >>> 0; | |
h -= n; | |
n += h * 0x100000000; // 2^32 | |
} | |
return (n >>> 0) * 2.3283064365386963e-10; // 2^-32 | |
}; | |
} | |
// amd | |
if (typeof define !== 'undefined' && define.amd) define(function() {return SimplexNoise;}); | |
// common js | |
if (typeof exports !== 'undefined') exports.SimplexNoise = SimplexNoise; | |
// browser | |
else if (typeof window !== 'undefined') window.SimplexNoise = SimplexNoise; | |
// nodejs | |
if (typeof module !== 'undefined') { | |
module.exports = SimplexNoise; | |
} | |
})(); | |
// tom's helpers below | |
/* helper function */ | |
String.prototype.hashCode = function() { | |
var hash = 0, i, chr; | |
if (this.length === 0) return hash; | |
for (i = 0; i < this.length; i++) { | |
chr = this.charCodeAt(i); | |
hash = ((hash << 5) - hash) + chr; | |
hash |= 0; // Convert to 32bit integer | |
} | |
return hash; | |
}; | |
/* | |
getNoiseValue arguments: | |
x: current grid location across | |
y: current grid location down | |
loop: can be any value from 0-1 and will loop | |
name: the "name" of the lookup table. probably change this each time. | |
min/max: the minimum and maximum of the value to return | |
smoothness: 1 means elements are not related. larger numbers cause groupings. | |
*/ | |
var global_simplex = null; | |
function getNoiseValue(x, y, loop, name, min, max, smoothness) { | |
if (global_simplex === null) { | |
global_simplex = new SimplexNoise(); | |
} | |
let hashNumber = name.hashCode(); | |
let xoff = Math.cos(2*PI*loop + (0.01 * hashNumber)); | |
let yoff = Math.sin(2*PI*loop + (0.01 * hashNumber)); | |
let noiseVal = global_simplex.noise4D(x / smoothness, y / smoothness, xoff + hashNumber, yoff); | |
// let noiseVal = noise(x / smoothness, y / smoothness, xoff + hashNumber); | |
return map(noiseVal, -1, 1, min, max); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<head> | |
<style> body {padding: 0; margin: 0;} </style> | |
</head> | |
<body style="background-color:white"> | |
<img src="sketch.jpg" width="960" height="480"/> | |
<p> | |
<a href="code.html">code</a> | |
</body> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function resetFocusedRandom() { | |
return Math.seedrandom(arguments); | |
} | |
function focusedRandom(min, max, focus, mean) { | |
// console.log("hello") | |
if(max === undefined) { | |
max = min; | |
min = 0; | |
} | |
if(focus === undefined) { | |
focus = 1.0; | |
} | |
if(mean === undefined) { | |
mean = (min + max) / 2.0; | |
} | |
if(focus == 0) { | |
return d3.randomUniform(min, max)(); | |
} | |
else if(focus < 0) { | |
focus = -1 / focus; | |
} | |
let sigma = (max - min) / (2 * focus); | |
let val = d3.randomNormal(mean, sigma)(); | |
if (val >= min && val < max) { | |
return val; | |
} | |
return d3.randomUniform(min, max)(); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// note: this file is poorly named - it can generally be ignored. | |
// helper functions below for supporting blocks/purview | |
function saveBlocksImages(doZoom) { | |
if(doZoom == null) { | |
doZoom = false; | |
} | |
// generate 960x500 preview.jpg of entire canvas | |
// TODO: should this be recycled? | |
var offscreenCanvas = document.createElement('canvas'); | |
offscreenCanvas.width = 960; | |
offscreenCanvas.height = 500; | |
var context = offscreenCanvas.getContext('2d'); | |
// background is flat white | |
context.fillStyle="#FFFFFF"; | |
context.fillRect(0, 0, 960, 500); | |
context.drawImage(this.canvas, 0, 0, 960, 500); | |
// save to browser | |
var downloadMime = 'image/octet-stream'; | |
var imageData = offscreenCanvas.toDataURL('image/jpeg'); | |
imageData = imageData.replace('image/jpeg', downloadMime); | |
p5.prototype.downloadFile(imageData, 'preview.jpg', 'jpg'); | |
// generate 230x120 thumbnail.png centered on mouse | |
offscreenCanvas.width = 230; | |
offscreenCanvas.height = 120; | |
// background is flat white | |
context = offscreenCanvas.getContext('2d'); | |
context.fillStyle="#FFFFFF"; | |
context.fillRect(0, 0, 230, 120); | |
if(doZoom) { | |
// pixelDensity does the right thing on retina displays | |
var pd = this._pixelDensity; | |
var sx = pd * mouseX - pd * 230/2; | |
var sy = pd * mouseY - pd * 120/2; | |
var sw = pd * 230; | |
var sh = pd * 120; | |
// bounds checking - just displace if necessary | |
if (sx < 0) { | |
sx = 0; | |
} | |
if (sx > this.canvas.width - sw) { | |
sx = this.canvas.width - sw; | |
} | |
if (sy < 0) { | |
sy = 0; | |
} | |
if (sy > this.canvas.height - sh) { | |
sy = this.canvas.height - sh; | |
} | |
// save to browser | |
context.drawImage(this.canvas, sx, sy, sw, sh, 0, 0, 230, 120); | |
} | |
else { | |
// now scaledown | |
var full_width = this.canvas.width; | |
var full_height = this.canvas.height; | |
context.drawImage(this.canvas, 0, 0, full_width, full_height, 0, 0, 230, 120); | |
} | |
imageData = offscreenCanvas.toDataURL('image/png'); | |
imageData = imageData.replace('image/png', downloadMime); | |
// call this function after 1 second | |
setTimeout(function(){ | |
p5.prototype.downloadFile(imageData, 'thumbnail.png', 'png'); | |
}, 1000); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function p5recorder(numFrames, filename, delay, repeat, buffersPerFrame) { | |
this.numFrames = numFrames; | |
// all other arguments are optional | |
if(filename) { | |
this.filename = filename; | |
} | |
else { | |
this.filename = "download.gif"; | |
} | |
if(delay) { | |
this.delay = delay; | |
} | |
else { | |
this.delay = 25; //go to next frame every 25 milliseconds | |
} | |
if(repeat) { | |
this.repeat = repeat; | |
} | |
else { | |
this.repeat = 0; //0 -> loop forever | |
} | |
if(buffersPerFrame) { | |
this.buffersPerFrame = buffersPerFrame; | |
} | |
else { | |
this.buffersPerFrame = 1; | |
} | |
this.encoder = new GIFEncoder(); | |
this.offscreenCanvas = document.createElement('canvas'); | |
this.offscreenCanvas.width = width; | |
this.offscreenCanvas.height = height; | |
this.offscreenContext = this.offscreenCanvas.getContext('2d'); | |
this.framesRecorded = 0; | |
this.buffersRecorded = 0; | |
this.imageAccumulator = null; | |
this.encoder_has_started = false; | |
pixelDensity(1); | |
this.addBuffer = function() { | |
if(!this.encoder_has_started) { | |
this.encoder.setRepeat(this.repeat); | |
this.encoder.setDelay(this.delay); | |
this.encoderResult = this.encoder.start(); | |
this.encoder_has_started = true; | |
} | |
let display_text = "Recording: " + (this.framesRecorded+1) + " / " + this.numFrames; | |
if (this.framesRecorded < this.numFrames) { | |
// background is flat white | |
this.offscreenContext.fillStyle="#FFFFFF"; | |
this.offscreenContext.fillRect(0, 0, width, height); | |
this.offscreenContext.drawImage(canvas, 0, 0, width, height); | |
if (this.buffersPerFrame > 1) { | |
display_text = "Recording: " + (this.buffersRecorded+1) + " / " + this.buffersPerFrame + " : " + (this.framesRecorded+1) + " / " + this.numFrames; | |
// each output image is made up of several input frames averaged together | |
if (this.buffersRecorded == 0) { | |
// initialize a new output Image | |
this.imageAccumulator = new Array(width * height); | |
for (let i=0; i<width*height; i++) { | |
this.imageAccumulator[i] = [0, 0, 0]; | |
} | |
} | |
loadPixels(); | |
for (let i=0; i<pixels.length/4; i++) { | |
// print(i); | |
// print(imageAccumulator[i]) | |
// print(pixels[i]) | |
this.imageAccumulator[i][0] += pixels[i*4+0]; | |
this.imageAccumulator[i][1] += pixels[i*4+1]; | |
this.imageAccumulator[i][2] += pixels[i*4+2]; | |
} | |
this.buffersRecorded = this.buffersRecorded + 1; | |
if(this.buffersRecorded == this.buffersPerFrame) { | |
// record this version and increment framesRecorded | |
loadPixels(); | |
for (let i=0; i<pixels.length/4; i++) { | |
pixels[i*4+0] = int(this.imageAccumulator[i][0] * 1.0/this.buffersPerFrame); | |
pixels[i*4+1] = int(this.imageAccumulator[i][1] * 1.0/this.buffersPerFrame); | |
pixels[i*4+2] = int(this.imageAccumulator[i][2] * 1.0/this.buffersPerFrame); | |
pixels[i*4+3] = 255; | |
} | |
updatePixels(); | |
this.imageAccumulator = null; | |
// reload this version onto the offscreen buffer | |
this.offscreenContext.fillStyle="#FFFFFF"; | |
this.offscreenContext.fillRect(0, 0, width, height); | |
this.offscreenContext.drawImage(canvas, 0, 0, width, height); | |
this.encoder.addFrame(this.offscreenContext); | |
this.framesRecorded = this.framesRecorded + 1; | |
this.buffersRecorded = 0; | |
} | |
} | |
else { | |
this.encoder.addFrame(this.offscreenContext); | |
this.framesRecorded = this.framesRecorded + 1; | |
} | |
if(this.framesRecorded == this.numFrames) { | |
this.encoder.finish(); | |
this.encoder.download(this.filename); | |
} | |
} | |
else { | |
display_text = "Recording: done"; | |
} | |
fill(255, 0, 0); | |
textSize(48); | |
text(display_text, 50, height-20); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment