Skip to content

Instantly share code, notes, and snippets.

@dribnet
Forked from uwcc/.block
Last active July 30, 2019 01:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dribnet/127e6785639ed89f3cca5faf5e974b86 to your computer and use it in GitHub Desktop.
Save dribnet/127e6785639ed89f3cca5faf5e974b86 to your computer and use it in GitHub Desktop.
MDDN342 Assignment 1: Living Wallpaper
license: mit
height: 480

Scene

example of how to combine techniques into a cohesive scene.

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;
}
var canvasWidth = 1920;
var canvasHeight = 960;
var canvasWidth = 960;
var canvasHeight = 480;
var canvasWidth = 3840;
var canvasHeight = 1920;
<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_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>
<a href="code_xl.html">(extra large)</a><br>
</body>
// tree https://editor.p5js.org/mtchl/sketches/ryyW2U5Fx
// leaf https://editor.p5js.org/projects/SkBQYKdVW
// note: canvasWidth and canvasHeight will be defined before this script runs
const num_across = 20;
const num_down = 10;
const cell_width = canvasWidth / num_across;
const cell_height = canvasHeight / num_down;
let x_grid_locations = [];
let y_grid_locations = [];
const frameMax = 60;
let recording = false;
let gifRecorder = null;
const buffersPerFrame = 3;
let random_seed = 0;
/* 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.
*/
function getNoiseValue(x, y, loop, name, min, max, smoothness) {
let hashNumber = name.hashCode();
let xoff = cos(2*PI*loop);
let yoff = sin(2*PI*loop);
let noiseVal = noise(xoff + x / smoothness, yoff + y / smoothness, (0 + hashNumber));
return map(noiseVal, 0, 1, min, max);
}
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');
storeGridPoints();
random_seed = random();
}
function mousePressed() {
if(recording == false) {
recording = true;
gifRecorder = new p5recorder(frameMax, "wallpaper.gif", 25, 0, buffersPerFrame);
}
}
function branch(depth){
if (depth < 10) {
line(0,0,0,-height/12); // draw a line going up
{
translate(0,-height/12); // move the space upwards
rotate(random(-0.05,0.05)); // random wiggle
if (random(1.0) < 0.6){ // branching
rotate(0.3); // rotate to the right
scale(0.8); // scale down
push(); // now save the transform state
branch(depth + 1); // start a new branch!
pop(); // go back to saved state
rotate(-0.6); // rotate back to the left
push(); // save state
branch(depth + 1); // start a second new branch
pop(); // back to saved state
}
else { // no branch - continue at the same depth
branch(depth);
}
}
}
}
function drawLeaf() {
push();
translate(-50, -50);
stroke(50,140,48);
strokeWeight(2);
arc(50, 60, 40, 40, 0, PI, OPEN);
noFill();
arc(65, 60, 40, 80, PI/1.5, 3*PI/2, OPEN);//leaf vein
arc(65,60,70,80,PI,3*PI/2,OPEN);
arc(70,20,10,80,PI/2,PI,OPEN);
fill(50,140,48);
strokeWeight(1);
triangle(46, 75, 69, 62, 44, 68);
triangle(44, 68, 32, 60, 46, 75);
triangle(45, 60, 65, 44, 45, 51);
triangle(45, 60, 33, 44, 45, 51);
triangle(49,36,43,30,47,40);
triangle(49,36,65,31,47,40);
pop();
}
function draw () {
let animation_max_frames = frameMax;
if(recording) {
animation_max_frames = frameMax * buffersPerFrame;
}
let cur_frame = frameCount % animation_max_frames;
let cur_frac = map(cur_frame, 0, animation_max_frames, 0, 1);
background("#99d9ff");
fill(0, 130, 0);
rect(0, 3*height/4, width, height);
fill(0);
stroke(0);
resetFocusedRandom(random_seed);
strokeWeight(int(0.02 * height));
for(let i=0; i<4; i++) {
let x_disp = map(i, -1, 4, 0, width);
push();
translate(x_disp, 0.90 * height);
branch(0);
pop();
}
noStroke();
// debug show the grid
/*
stroke(10);
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];
let y2 = y_grid_locations[i+1];
let x2 = x_grid_locations[j+1];
line(x1, y1, x2, y1);
line(x1, y1, x1, y2);
}
}
*/
// draw the background circles
let white_ellipse_radius = 0.78 * cell_width;
let inner_ellipse_radius = 0.50 * cell_width;
for (let i = 2; i < num_down-4; 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];
let xloc = x1 + cell_width/2;
let yloc = y1 + cell_height/2;
fill("#ffffff");
// ellipse(xloc, yloc, white_ellipse_radius);
fill("#99d9ff");
let cur_shift_x = getNoiseValue(i, j, cur_frac, "cur_shift_x", -0.4, 0.4, 10);
let cur_shift_y = getNoiseValue(i, j, cur_frac, "cur_shift_y", -0.4, 0.4, 10);
// let cur_radius = getNoiseValue(i, j, cur_frac, "cur_radius", 0.3, 0.7, 100);
// inner_ellipse_radius = cur_radius * cell_width;
let shifted_x = xloc + cur_shift_x * cell_width;
let shifted_y = yloc + cur_shift_y * cell_width;
// ellipse(shifted_x, shifted_y, inner_ellipse_radius);
let leaf_spin = getNoiseValue(i, j, 0, "leaf_spin", -PI/4, PI/2, 1);
push();
translate(shifted_x, shifted_y);
rotate(leaf_spin);
let scale_factor = 0.8 * height / 480;
scale(scale_factor);
drawLeaf();
pop();
}
}
/*
// draw the brown and red circles
let teal_ellipse_radius = 0.7 * cell_width;
let bright2_ellipse_radius = 0.58 * cell_width;
let violet_ellipse_radius = 0.38 * cell_width;
for (let i = 0; i < num_down+1; 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("#98ffec");
ellipse(x1, y1, teal_ellipse_radius);
fill("#feffff");
ellipse(x1, y1, bright2_ellipse_radius);
fill("#A1DEE5");
ellipse(x1, y1, violet_ellipse_radius);
}
}
*/
// drawLeaf();
if(recording) {
gifRecorder.addBuffer();
}
}
function keyTyped() {
if (key == '!') {
saveBlocksImages();
}
else if (key == '@') {
saveBlocksImages(true);
}
}
<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_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>
<a href="code_xl.html">(extra large)</a><br>
</body>
<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 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 - kweiner@fmsware.com)
* @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;
};
<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_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>
<a href="code_xl.html">(extra large)</a><br>
</body>
/**
* 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 - kweiner@fmsware.com)
* @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 (mgardi@watdcsu.waterloo.edu)
// 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;
};
/*
* 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 - kweiner@fmsware.com)
* @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;
};
/*! p5.func.js v0.0.1 2017-05-27 */
/**
* @module p5.func
* @submodule p5.func
* @for p5.func
* @main
*/
/**
* p5.func
* R. Luke DuBois (dubois@nyu.edu)
* 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
* pickrand() : return a random element from an array
* createArray()/normalizeArray()/resizeArray()/multiplyArray()/addArray()/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) since 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) {
//
// 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;
// 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;
this.bandwidth = 2 / this.bufferSize * this.sampleRate / 2; // used for getBandFrequency()
// data arrays for the magnitude / phase spectrum and the raw real/imaginary
// FFT data. 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.spectrum = new Float64Array(this.bufferSize/2);
this.phase = new Float64Array(this.bufferSize/2);
this.real = new Float64Array(this.bufferSize);
this.imag = new Float64Array(this.bufferSize);
// 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.
p5.FastFourierTransform.prototype.getBandFrequency = function(_index) {
return this.bandwidth * _index + this.bandwidth / 2;
}
// 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.spectrum[i] = mag;
this.phase[i] = phase;
}
}
// 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;
}
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);
}
// pick a random element from an array
p5.prototype.pickrand = function(_array) {
return(_array[floor(random(_array.length))]);
}
// 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);
}
// 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
{
"commits": [
{
"sha": "a1ad9f3252e5ba310dfd466a3bd1881cf6c2fba6",
"name": "Scene Example"
},
{
"sha": "67b2f59ffcd38152bd68a4b81f9bdbbd33137596",
"name": "Noise Example"
},
{
"sha": "4da692cf587282b02fc6d435ddb28c328b51fb5e",
"name": "Add Easing to Animation"
},
{
"sha": "46b9ea2cc3e8f48758f0b6431599e249ca9f2b57",
"name": "Easing Functions A vs B"
},
{
"sha": "04b9dd2e025e3b13c6f9f44a8c798db1ae1dd2af",
"name": "Easing Functions"
},
{
"sha": "fcc3d67fd542cf7b60dbca7e55402a1754eb6e07",
"name": "Blue Tilt"
},
{
"sha": "5aa826fe152deec528ce3a9d3b063e28077b2d26",
"name": "Motion Blur"
},
{
"sha": "e06aa62825d4e3e86c8aa16c12ea72dc9845538b",
"name": "Grid Flowing Loop"
},
{
"sha": "7c10f0d6325f5802afa28a0e06e6c8efc1f25c9b",
"name": "Grid Animation Loop"
},
{
"sha": "b69cf3a2fcfd9470d275be6947189daec5eea20c",
"name": "First Animation Loop"
},
{
"sha": "da8e912a0a6191682463d8b8090341e920e86dd3",
"name": "Implemented Fran's Sketch"
},
{
"sha": "4be3fe246426ce5d1bb859c915008f8a6b1e5c17",
"name": "Third Grid"
},
{
"sha": "935275ded48a9fef7cfab89259a021a39f97fdb4",
"name": "Second Grid"
},
{
"sha": "e44af7a66daa21cb0adcffaf3c95416b911da43d",
"name": "First Grid"
},
{
"sha": "5929eba90250f698d2b8145920d80323ec12bb3d",
"name": "Wallpaper Sketch"
},
{
"sha": "89c49cb528e74654a59ddfa745b1aa764a81b510",
"name": "Starting Point"
}
]
}
<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>
let is_animating = false;
let current_frame = 0;
const max_frames = 200;
const e = new p5.Ease();
function setup () {
createCanvas(960, 480);
print(e.listAlgos());
}
function draw () {
background(128);
stroke(0);
fill(200);
textSize(48);
var display_text = "no";
if(is_animating) {
display_text = "" + current_frame + " / " + max_frames;
}
text(display_text, 50, height-20);
const box_size = 50;
let current_fraction = current_frame / max_frames;
// example1: normal linear interpolation without easing
const box_1_y = 50;
let box_1_pos = map(current_fraction, 0, 1, 0, width-box_size);
rect(box_1_pos, box_1_y, box_size, box_size);
// example2: "circularIn" easing interpolation
const box_2_y = 125;
let new_fraction2 = e.circularIn(current_fraction);
let box_2_pos = map(new_fraction2, 0, 1, 0, width-box_size);
rect(box_2_pos, box_2_y, box_size, box_size);
// example3: "doubleCircularOgee" easing interpolation (with parameter 0.5)
const box_3_y = 200;
let new_fraction3 = e.doubleCircularOgee(current_fraction, 0.5);
let box_3_pos = map(new_fraction3, 0, 1, 0, width-box_size);
rect(box_3_pos, box_3_y, box_size, box_size);
// example4: "bounceOut" easing interpolation
const box_4_y = 275;
let new_fraction4 = e.bounceOut(current_fraction, 10);
let box_4_pos = map(new_fraction4, 0, 1, 0, width-box_size);
rect(box_4_pos, box_4_y, box_size, box_size);
// example5: combine example3 with example4
const box_5_y = 350;
let new_fraction5a = e.doubleCircularOgee(current_fraction, 0.5);
let new_fraction5b = e.bounceOut(new_fraction5a, 10);
let box_5_pos = map(new_fraction5b, 0, 1, 0, width-box_size);
rect(box_5_pos, box_5_y, box_size, box_size);
if(is_animating && current_frame < max_frames) {
// saveBlocksImages();
current_frame = current_frame + 1;
}
}
function mousePressed() {
if(is_animating) {
is_animating = false;
current_frame = 0;
}
else {
is_animating = true;
}
}
function keyTyped() {
if (key == '!') {
saveBlocksImages();
}
}
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)();
}
// 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);
}
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