I'm here again because I'm still having trouble trying to implement a flood fill tool in my drawing app.
I am attempting to make a fairly simple 2d paint application using p5.js with each drawing tool having its own constructor function. I've been having trouble understanding what I am doing wrong and why it is not working which is causing lots of frustration.
I have read a few articles on here and followed a tutorial on youtube but I still don't quite understand it. I will include what I have done so far so you can see. Specifically, I am not sure what to write for the draw function. I would like the flood fill to happen on mouseX and mouseY coordinates when the mouse is pressed. Also, I would like for the target colour to be a colour selected from a separate constructor function ColourPalette().
HTML:
<!DOCTYPE html>
<html>
<head>
<script src="lib/p5.min.js"></script>
<script src="lib/p5.dom.js"></script>
<script src="sketch.js"></script>
<!-- add extra scripts below -->
...
<script src="fillTool.js"></script>
</body>
</html>
The sketch file:
function setup() {
//create a canvas to fill the content div from index.html
canvasContainer = select('#content');
var c = createCanvas(canvasContainer.size().width, canvasContainer.size().height);
c.parent("content");
//create helper functions and the colour palette
helpers = new HelperFunctions();
colourP = new ColourPalette();
...
toolbox.addTool(new FillTool());
background(255);
}
function draw() {
//call the draw function from the selected tool.
//if there isn't a draw method the app will alert the user
if (toolbox.selectedTool.hasOwnProperty("draw")) {
toolbox.selectedTool.draw();
} else {
alert("it doesn't look like your tool has a draw method!");
}
}
The flood fill constructor function I need help with. I'm getting "Uncaught ReferenceError: floodFill is not defined" for line 112 (within the draw function) in the console and I'm a little stuck on how to fix it.:
function FillTool() {
//set an icon and a name for the object
this.icon = "assets/freehand.jpg";
this.name = "FillTool";
var colourNew = ColourPalette(colourP); //Placeholder - How do I do this?
function getPixelData(x,y){
var colour = [];
for (var i = 0; i < d; i++) {
for (var j = 0; j < d; j++) {
idx = 4 * ((y * d + j) * width * d + (x * d + i));
colour[0] = pixels[idx];
colour[1] = pixels[idx+1];
colour[2] = pixels[idx+2];
colour[3] = pixels[idx+3];
}
}
return colour;
}
function setPixelData(x, y, colourNew) {
for (var i = 0; i < d; i++) {
for (var j = 0; j < d; j++) {
idx = 4 * ((y * d + j) * width * d + (x * d + i));
pixels[idx] = colourNew[0];
pixels[idx+1] = colourNew[1];
pixels[idx+2] = colourNew[2];
pixels[idx+3] = colourNew[3];
}
}
}
function matchColour(xPos,yPos,oldColour){
var current = get(xPos,yPos);
if(current[0] == oldColour[0] && current[1] == oldColour[1] && current[2] == oldColour[2] && current[3] == oldColour[3]){
return true;
}
}
function checkPixel(x1,y1,pixelArray){
for (var i = 0 ; i < pixelArray.length; i+=2){
if(x1 == pixelArray[i] && y1 == pixelArray[i+1]){
return false;
}
else {
console.log(pixelArray.length)
return true;
}
}
}
function floodFill (xPos,yPos){
loadPixels();
colourOld = getPixelData(xPos, yPos);
var stack = [];
var pixelList = [];
stack.push(xPos,yPos);
pixelList.push(xPos,yPos);
console.log(stack);
while(stack.length > 0){
var yPos1 = stack.pop();
var xPos1 = stack.pop();
setPixelData(xPos1,yPos1,colourNew);
if(xPos1 + 1 <= width && xPos1 + 1 > 0 ){
if(matchColour(xPos1+1,yPos1,colourOld) && checkPixel(xPos1+1,yPos1,pixelList)){
stack.push(xPos1+1,yPos1);
pixelList.push(xPos1+1,yPos1);
}
}
if(xPos1+1 <= width && xPos1+1 > 0 ){
if(matchColour(xPos1-1,yPos1,colourOld) && checkPixel(xPos1-1,yPos1,pixelList)){
stack.push(xPos1-1,yPos1);
pixelList.push(xPos1-1,yPos1);
}
}
if(yPos1+1 <= height && yPos1+1 > 0 ){
if(matchColour(xPos1,yPos1+1,colourOld) && checkPixel(xPos1,yPos1+1,pixelList)){
stack.push(xPos1,yPos1+1);
pixelList.push(xPos1,yPos1+1);
}
}
if(yPos1-1 <= height && yPos1-1 > 0 ){
if(matchColour(xPos1,yPos1-1,colourOld) && checkPixel(xPos1,yPos1-1,pixelList)){
stack.push(xPos1,yPos1-1);
pixelList.push(xPos1,yPos1-1);
}
}
}
updatePixels();
console.log(pixelList);
}
}
this.draw = function() {
if(mouseIsPressed){
floodFill(mouseX,mouseY);
}
}
Sorry if its a bit of a mess, it's an accurate representation of my brain at the moment.
The function checkPixel was very very slow because pixelArray grows as you draw new pixels, so verifying if a new pixel was in the stack or had already been drawn took longer each time.
In javascript it is possible to use an object {} to store key/value pair like :
var colours = { 'red':{'r':255,'g':0,'b':0,'a':255}, 'black':{'r':0,'g':0,'b':0,'a':255} };
And calling the method hasOwnPorperty to verify the presence of the key is very fast.
colours.hasOwnPorperty('red') // is true
colours.hasOwnPorperty('green') // is false
If you had a 1,000,000 colours in colours, it would not take longer for hasOwnPorperty to find a colour in colours than if you had only 1 colour in colours. (It's O(1) has opposed to O(n) for your version of checkPixel)
Try clicking inside the circle ... or outside
let toolbox, d;
function setup() {
createCanvas(600, 400);
d = pixelDensity();
//create helper functions and the colour palette
//...
let colourRed = ColourPalette(255,0,0,255);
//...
toolbox = {'selectedTool': new FillTool() };
toolbox.selectedTool.setColour(colourRed);
background(255);
push();
strokeWeight(1);
stroke(0);
circle(75,75,100);
noStroke();
fill(0,255,0,255);
circle(125,75,100);
pop();
}
function draw() {
if (! toolbox.selectedTool.hasOwnProperty("draw")) {
alert("it doesn't look like your tool has a draw method!");
return;
}
toolbox.selectedTool.draw();
}
function FillTool() {
let self = this;
//set an icon and a name for the object
self.icon = "assets/freehand.jpg";
self.name = "FillTool";
self.colour = ColourPalette(0,0,0,255);
self.draw = function () {
if (mouseIsPressed) {
floodFill(mouseX, mouseY);
}
};
self.setColour = function (col) {
self.colour = col;
};
function matchColour (pos, oldColour) {
var current = getPixelData(pos.x, pos.y);
return ( current[0] === oldColour[0] && current[1] === oldColour[1]
&& current[2] === oldColour[2] && current[3] === oldColour[3] );
}
function getKey (pos) {
return ""+pos.x+"_"+pos.y;
}
function checkPixel(pos, positionSet) {
return ! positionSet.hasOwnProperty( getKey(pos) );
}
function floodFill (xPos, yPos) {
var stack = [];
var pixelList = {};
var first = {'x':xPos,'y':yPos};
stack.push( first );
pixelList[ getKey(first) ] = 1;
//console.log( JSON.stringify(stack) );
loadPixels();
var firstColour = getPixelData(xPos, yPos);
while (stack.length > 0) {
var pos1 = stack.pop();
setPixelData(pos1.x, pos1.y, self.colour);
var up = {'x':pos1.x, 'y':pos1.y-1};
var dn = {'x':pos1.x, 'y':pos1.y+1};
var le = {'x':pos1.x-1,'y':pos1.y};
var ri = {'x':pos1.x+1,'y':pos1.y};
if (0 <= up.y && up.y < height && matchColour(up, firstColour)) addPixelToDraw(up);
if (0 <= dn.y && dn.y < height && matchColour(dn, firstColour)) addPixelToDraw(dn);
if (0 <= le.x && le.x < width && matchColour(le, firstColour)) addPixelToDraw(le);
if (0 <= ri.x && ri.x < width && matchColour(ri, firstColour)) addPixelToDraw(ri);
}
updatePixels();
//console.log( JSON.stringify(pixelList) );
function addPixelToDraw (pos) {
if (checkPixel(pos, pixelList) ) {
stack.push( pos );
pixelList[ getKey(pos) ] = 1;
}
}
}
}
function ColourPalette (r,g,b,a) {
var self = (this !== window ? this : {});
if (arguments.length === 0) {
self['0'] = 0; self['1'] = 0; self['2'] = 0; self['3'] = 0;
} else if (arguments.length === 1) {
self['0'] = r[0]; self['1'] = r[1]; self['2'] = r[2]; self['3'] = r[3];
} else if (arguments.length === 4) {
self['0'] = r; self['1'] = g; self['2'] = b; self['3'] = a;
} else {
return null;
}
return self;
}
function getPixelData (x, y) {
var colour = [];
for (var i = 0; i < d; ++i) {
for (var j = 0; j < d; ++j) {
let idx = 4 * ((y * d + j) * width * d + (x * d + i));
colour[0] = pixels[idx];
colour[1] = pixels[idx+1];
colour[2] = pixels[idx+2];
colour[3] = pixels[idx+3];
}
}
return colour;
}
function setPixelData (x, y, colour) {
for (var i = 0; i < d; ++i) {
for (var j = 0; j < d; ++j) {
let idx = 4 * ((y * d + j) * width * d + (x * d + i));
pixels[idx] = colour[0];
pixels[idx+1] = colour[1];
pixels[idx+2] = colour[2];
pixels[idx+3] = colour[3];
}
}
}
body { background-color:#efefef; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.0.0/p5.min.js"></script>
Related
I added something to my code that's supposed to detect when an object (pixel) already exists, and then replace the color instead of creating a new one. However, for some reason, it just doesn't do anything.
Code:
var gridSize = 16;
var pixels = [];
var draw = function() {
background(255, 255, 255);
for (var i = 0; i < gridSize; i++) {
stroke(0, 0, 0);
line(400/gridSize*i, 0, 400/gridSize*i, 400);
line(0, 400/gridSize*i, 400, 400/gridSize*i);
}
for (var i = 0; i < pixels.length; i++) {
noStroke();
fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
rect(pixels[i][0]*(400/gridSize), pixels[i][1]*(400/gridSize), 400/gridSize, 400/gridSize);
}
document.getElementById("output").innerHTML = pixels;
fill(255, 0, 0);
text(alreadyExists, 200, 200);
};
var mousePressed = function() {
alreadyExists = false;
for (var i = 0; i < pixels.length; i++) {
if (pixels[i] === [ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]) {
alreadyExists = true;
pixels[i][2] = [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value];
}
}
if (!alreadyExists) {
pixels.push([ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]);
}
};
var mouseDragged = function() {
alreadyExists = false;
for (var i = 0; i < pixels.length; i++) {
if (pixels[i] === [ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]) {
alreadyExists = true;
pixels[i][2] = [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value];
}
}
if (!alreadyExists) {
pixels.push([ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [document.getElementById("color1").value, document.getElementById("color2").value, document.getElementById("color3").value]]);
}
};
When comparing arrays, you need to compare individually every item, in your case. I modified a the original example to minimize repetition of code blocks and to improve a bit speed and memory usage. You can also a working example here
The idea is to compare the pixels positions, encoded at index 0 for x and 1 for y. And then to compare the color channels, which are also encoded in an array so we need to then again compare individually every component.
The function samePixels in the following example is exactly doing that:
var samePixels = function (p1, p2) {
var samePosition = p1[0] === p2[0] && p1[1] === p2[1];
if (!samePosition) {
return false;
}
var colors1 = p1[2];
var colors2 = p2[2];
return (colors1[0] === colors2[0]) &&
(colors1[1] === colors2[1]) &&
(colors1[2] === colors2[2]);
}
Full source code for the js part:
var gridSize = 16;
var pixels = [];
var alreadyExists;
var color1 = document.getElementById('color1');
var color2 = document.getElementById('color2');
var color3 = document.getElementById('color3');
var draw = function() {
background(255, 255, 255);
for (var i = 0; i < gridSize; i++) {
stroke(0, 0, 0);
line(400/gridSize*i, 0, 400/gridSize*i, 400);
line(0, 400/gridSize*i, 400, 400/gridSize*i);
}
for (var i = 0; i < pixels.length; i++) {
stroke(0);
fill(pixels[i][2][0], pixels[i][2][1], pixels[i][2][2]);
rect(pixels[i][0]*(400/gridSize), pixels[i][1]*(400/gridSize), 400/gridSize, 400/gridSize);
}
document.getElementById("output").innerHTML = pixels;
fill(255, 0, 0);
text(alreadyExists, 200, 200);
};
var mousePressed = mouseDragged = function() {
alreadyExists = false;
closestPixel = [ceil(mouseX/(400/gridSize))-1, ceil(mouseY/(400/gridSize))-1, [color1.value, color2.value, color3.value]];
for (var i = 0; i < pixels.length; i++) {
if (samePixels(pixels[i], closestPixel)) {
alreadyExists = true;
pixels[i][2] = [color1.value, color2.value, color3.value];
break;
}
}
console.log('Does the pixel already exist?', alreadyExists);
if (!alreadyExists) {
pixels.push(closestPixel);
}
};
var samePixels = function (p1, p2) {
var samePosition = p1[0] === p2[0] && p1[1] === p2[1];
if (!samePosition) {
return false;
}
var colors1 = p1[2];
var colors2 = p2[2];
return (colors1[0] === colors2[0]) &&
(colors1[1] === colors2[1]) &&
(colors1[2] === colors2[2]);
}
I am making a tree with space-colonization algorithm in javascript. (with p5.js)
I followed the tutorial of https://www.youtube.com/watch?v=kKT0v3qhIQY&ab_channel=TheCodingTrain
or in C#
http://www.jgallant.com/procedurally-generating-trees-with-space-colonization-algorithm-in-xna/
I want to make a customized area (in this case a circle) where the leaves are generated randomly.
in Leaf.js I try to include some calculations of how the leaves get the random Coordinates within the circle.
So there is sketch.js , leaf.js, branch.js, tree.js
//leaf.js
function Leaf() {
function getChord(){
var r = 150;
var angle = random(0, 2 * PI);
var xChord = 2*r + sqrt(r) * cos(angle);
var yChord = r + sqrt(r) * sin(angle);
return (createVector(xChord, yChord));
}
this.pos = getChord();
this.reached = false;
// // var t = 2 * PI * random(0,1);
// // var r = sqrt(random(0,1));
// // var x = r * cos(t);
// // var y = r * sin(t);
// // this.pos = createVector(x, y);
// this.reached = false;
this.show = function() {
fill(58, 126, 34);
noStroke();
ellipse(this.pos.x, this.pos.y, 4, 4);
};
}
//branch.js
function Branch(parent, pos, dir) {
this.pos = pos;
this.parent = parent;
this.dir = dir;
this.origDir = this.dir.copy();
this.count = 0;
this.len = 3;
this.reset = function() {
this.dir = this.origDir.copy();
this.count = 0;
};
this.next = function() {
var nextDir = p5.Vector.mult(this.dir, this.len);
var nextPos = p5.Vector.add(this.pos, nextDir);
var nextBranch = new Branch(this, nextPos, this.dir.copy());
return nextBranch;
};
this.show = function() {
if (parent != null) {
stroke(151, 53, 48);
strokeWeight(1);
line(this.pos.x, this.pos.y, this.parent.pos.x, this.parent.pos.y);
}
};
}
And then in tree.js I push every leaf into leaves.
//tree.js
function Tree() {
this.leaves = [];
this.branches = [];
for (var i = 0; i < 1500; i++) {
this.leaves.push(new Leaf());
}
var pos = createVector(width / 2, height);
var dir = createVector(0, -1);
var root = new Branch(null, pos, dir);
this.branches.push(root);
var current = root;
var found = false;
while (!found) {
for (var i = 0; i < this.leaves.length; i++) {
var d = p5.Vector.dist(current.pos, this.leaves[i].pos);
if (d < max_dist) {
found = true;
}
}
if (!found) {
var branch = current.next();
current = branch;
this.branches.push(current);
}
}
this.grow = function() {
for (var i = 0; i < this.leaves.length; i++) {
var leaf = this.leaves[i];
var closestBranch = null;
var record = max_dist;
for (var j = 0; j < this.branches.length; j++) {
var branch = this.branches[j];
var d = p5.Vector.dist(leaf.pos, branch.pos);
if (d < min_dist) {
leaf.reached = true;
closestBranch = null;
break;
} else if (d < record) {
closestBranch = branch;
record = d;
}
}
if (closestBranch != null) {
var newDir = p5.Vector.sub(leaf.pos, closestBranch.pos);
newDir.normalize();
closestBranch.dir.add(newDir);
closestBranch.count++;
}
}
for (var i = this.leaves.length - 1; i >= 0; i--) {
if (this.leaves[i].reached) {
this.leaves.splice(i, 1);
}
}
for (var i = this.branches.length - 1; i >= 0; i--) {
var branch = this.branches[i];
if (branch.count > 0) {
branch.dir.div(branch.count + 1);
this.branches.push(branch.next());
branch.reset();
}
}
};
this.show = function() {
for (var i = 0; i < this.leaves.length; i++) {
this.leaves[i].show();
}
for (var i = 0; i < this.branches.length; i++) {
this.branches[i].show();
}
};
}
//sketch.js
var tree;
var max_dist = 30;
var min_dist = 10;
function setup() {
createCanvas(600, 600);
tree = new Tree();
}
function draw() {
background(111, 149, 231);
tree.show();
tree.grow();
}
Some how in the function getChord I think there is an infinite loop or having trouble getting the random value? Because it is not loading at all... It works when I change this.pos = getChord(); to this.pos = createVector(random(width),random(height-100);
Does anyone know how to solve this?
Or how to write codes to make an area of the circle where the leaves can be generated?
Thank you!
I'm developing a little game of nothing at all more commonly called "Pathfinding"; and I am crashing on a small error that I have never encountered (I am young dev);
I have searched everywhere but I do not understand why this error appears.
I am experiencing this error:
Uncaught TypeError: current.distance is not a function
at search_min_distance (pathfinding.js:127)
at game (pathfinding.js:151)
at HTMLDocument. (pathfinding.js:173)
document.addEventListener("DOMContentLoaded", function() {
const NBR_POINT = 10;
const MIN_SPACING = 100;
const HEIGHT = window.innerHeight;
const WIDTH = window.innerWidth;
class point {
constructor(x, y, name, id) {
this.x = x;
this.y = y;
this.part = document.createElement("div");
this.part.className = name;
this.part.id = id;
}
distance(cmp) {
return Math.sqrt(Math.pow(cmp.x - this.x, 2) + Math.pow(cmp.y - this.y, 2));
}
}
function random(mode) {
if (!mode)
return Math.random() * ((WIDTH - 100) - 100) + 100;
else
return Math.random() * ((HEIGHT - 100) - 100) + 100;
}
function printPoint(stk, crt) {
var block = 0;
do {
block = 0;
var x = random(0);
var y = random(1);
if (stk.length != 0) {
for (let i = 0; i < stk.length; i++) {
if (x < (stk[i].x + MIN_SPACING) && x > (stk[i].x - MIN_SPACING) && y < (stk[i].y + MIN_SPACING) && y > (stk[i].y - MIN_SPACING)) {
block = 1;
break;
}
}
}
} while (block == 1);
var ids = stk.length + 1;
if (crt == "current")
var p = new point(x, y, "point courant", ids);
else
var p = new point(x, y, "point", ids);
p.part.style.bottom = p.y + "px";
p.part.style.left = p.x + "px";
document.body.appendChild(p.part);
return p;
}
function polyPoint() {
var stk = new Array();
for (let i = 0; i < NBR_POINT - 1; i++) {
stk.push(printPoint(stk, null));
}
printPoint(stk, "current");
return stk;
}
function imp_session() {
var dark_status = sessionStorage.getItem('dark_mode');
if (dark_status == 1)
document.body.classList.add("darkmode");
if (dark_status == 0)
document.body.classList.remove("darkmode");
}
function srv_DarkMode() {
document.addEventListener("keypress", function(event) {
let keypress = event.key;
var dark_status = sessionStorage.getItem('dark_mode');
if (keypress == "d") {
if (dark_status == null) {
dark_status = 0;
sessionStorage.setItem('dark_mode', dark_status);
}
if (dark_status == 1) {
this.body.classList.remove("darkmode");
dark_status = 0;
//To be able to delete sessions and not add them afterwards and error duplicate key.
sessionStorage.removeItem('dark_mode');
sessionStorage.setItem('dark_mode', dark_status);
} else {
this.body.classList.add("darkmode");
dark_status = 1;
//To be able to delete sessions and not add them afterwards and error duplicate key.
sessionStorage.removeItem('dark_mode');
sessionStorage.setItem('dark_mode', dark_status);
}
}
});
}
function search_min_distance(stk, current) {
let min = current.distance(stk[0]);
let tmp;
let real = min;
for (let i = 1; i < stk.length; i++) {
if (stk[i].id = current.id)
continue;
tmp = current.distance(stk[i]);
if (tmp < min)
real = i;
}
return real;
}
function game(stk) {
var cp = document.getElementsByClassName('courant');
cp[0].addEventListener("click", function(event) {
alert("txt");
});
var distId = search_min_distance(stk, cp[0]);
var p = document.getElementsByClassName('point');
for (let i = 0; i < p.length; i++) {
if (p[i].id != cp[0].id || p[i].id != distId) {
p[i].addEventListener("click", function(event) {
alert("txt");
});
}
}
}
function init() {
imp_session();
srv_DarkMode();
return polyPoint();
}
game(init());
});
-> current and stk [0] are indeed of "type" point.
All the remaining code does not seem important to me, but if necessary I will provide it.
I thank you in advance ! Be indulgent...
The point you are getting using the getElement function just returns
the js object, it does not contain any functions from the class.
You need to create a new class instance from this obtained point as follows inside the search_min_distance function
const { x, y, name, id } = current;
const currentPoint = new Point(x, y, name, id);
let min = currentPoint.distance(stk[0]);
I suggest you write a util function to calculate the distance between two points, passing the two points as arguments. It will be a cleaner approach.
Bind distance function to this inside constructor
class point {
constructor(x, y, name, id) {
this.x = x;
this.y = y;
this.part = document.createElement("div");
this.part.className = name;
this.part.id = id;
// bind distance method
this.distance = this.distance.bind(this);
}
distance(cmp) {
return Math.sqrt(Math.pow(cmp.x - this.x, 2) + Math.pow(cmp.y - this.y, 2));
}
}
The name of error is "TypeError". And That's not just for fun. Possible problems in search_min_distance:
stk is not defined or its length is 0
stk is not array of points (typeof point[])
point is not defined or it's not point (typeof point)
Sure problems:
point.id is not defined, it's only in point.part
if (stk[i].id = current.id)
to
if (stk[i].part.id = current.part.id)
Your code is only errors.
Finally all this for a simple mistake of inattention; I took dom for my array and put an assignment where there should have been an equal .. thanks everyone.
if (stk[i].id = current.id)
as
if (stk[i].id == current.id)
and
var distId = search_min_distance(stk, cp[0]);
as
var distId = search_min_distance(stk, stk[cp[0].id]);
How do I change voice pitch without noise using web audio in JavaScript?
I tried the code below.
I got my voice with noisy and bit echo mixed.
I want a voice module which is like noiseless.
var pitchShifter = (function () {
var context, audioContext, pitchShifterProcessor, spectrumAudioAnalyser,
sonogramAudioAnalyser;
var validGranSizes = [256, 512, 1024, 2048, 4096, 8192],
grainSize = validGranSizes[1],
pitchRatio = 2.0,
overlapRatio = 0.50;
var filter, compressor;
hannWindow = function (length) {
var window = new Float32Array(length);
for (var i = 0; i < length; i++) {
window[i] = 0.5 * (1 - Math.cos(2 * Math.PI * i / (length - 1)));
}
return window;
};
linearInterpolation = function (a, b, t) {
return a + (b - a) * t;
};
initProcessor = function () {
if (navigator.getUserMedia) {
navigator.getUserMedia({
audio: true
},
function (stream) {
var convolver = audioContext.createConvolver();
compressor = audioContext.createDynamicsCompressor();
compressor.threshold.value = -50;
compressor.knee.value = 40;
compressor.ratio.value = 12;
compressor.reduction.value = -20;
compressor.attack.value = 0;
compressor.release.value = 0.25;
filter = audioContext.createBiquadFilter();
filter.Q.value = 8.30;
filter.frequency.value = 355;
filter.gain.value = 3.0;
filter.type = 'bandpass';
filter.connect(compressor);
compressor.connect(audioContext.destination);
filter.connect(audioContext.destination);
var microphone_stream = audioContext.createMediaStreamSource(
stream);
microphone_stream.connect(filter);
var source = audioContext.createBufferSource();
microphone_stream.connect(pitchShifterProcessor);
},
function (e) {
alert('Error capturing audio.');
}
);
}
if (pitchShifterProcessor) {
pitchShifterProcessor.disconnect();
}
if (audioContext.createScriptProcessor) {
pitchShifterProcessor = audioContext.createScriptProcessor(
grainSize, 1, 1);
} else if (audioContext.createJavaScriptNode) {
pitchShifterProcessor = audioContext.createJavaScriptNode(grainSize,
1, 1);
}
pitchShifterProcessor.buffer = new Float32Array(grainSize * 2);
pitchShifterProcessor.grainWindow = hannWindow(grainSize);
pitchShifterProcessor.onaudioprocess = function (event) {
var inputData = event.inputBuffer.getChannelData(0);
var outputData = event.outputBuffer.getChannelData(0);
for (i = 0; i < inputData.length; i++) {
// Apply the window to the input buffer
inputData[i] *= this.grainWindow[i];
// Shift half of the buffer
this.buffer[i] = this.buffer[i + grainSize];
// Empty the buffer tail
this.buffer[i + grainSize] = 0.0;
}
// Calculate the pitch shifted grain re-sampling and looping the input
var grainData = new Float32Array(grainSize * 2);
for (var i = 0, j = 0.0; i < grainSize; i++, j += pitchRatio) {
var index = Math.floor(j) % grainSize;
var a = inputData[index];
var b = inputData[(index + 1) % grainSize];
grainData[i] += linearInterpolation(a, b, j % 1.0) * this.grainWindow[
i];
}
// Copy the grain multiple times overlapping it
for (i = 0; i < grainSize; i += Math.round(grainSize * (1 -
overlapRatio))) {
for (j = 0; j <= grainSize; j++) {
this.buffer[i + j] += grainData[j];
}
}
// Output the first half of the buffer
for (i = 0; i < grainSize; i++) {
outputData[i] = this.buffer[i];
}
};
pitchShifterProcessor.connect(audioContext.destination);
};
return {
init: function () {
if ('AudioContext' in window) {
audioContext = new AudioContext();
} else {
alert('Your browser does not support the Web Audio API');
return;
}
initProcessor();
}
}
}());
window.requestAnimFrame = (function () {
return (window.requestAnimationFrame ||
window.webkitRequestAnimationFrame ||
window.mozRequestAnimationFrame ||
function (callback) {
window.setTimeout(callback, 1000 / 60);
});
})();
window.addEventListener("DOMContentLoaded", pitchShifter.init, true);
You can use the PitchShifter in SoundTouchJS for this. I currently use this in my CDGPlayer for changing the 'key' for a user.
I want to draw a self-similarity matrix based on data points, I'm wondering if there is a JavaScript plugin to render it, similar to xy.js or chart.js
this chart should look like:
searching google keeps giving me results using D3 or cubism which seem to target realtime data flow, while I only have a single list of numbers that does not change.
I did not manage to find an answer, so I made my own (so plugins still appreciated)
(function(w){
function extend (target, source) {
target = target || {};
for (var prop in source) {
if (typeof source[prop] === 'object') {
target[prop] = extend(target[prop], source[prop]);
} else {
target[prop] = source[prop];
}
}
return target;
}
w.Squares = function(ctx, options){
var default_opts = {
};
options = extend(default_opts, options);
if(!this instanceof Squares){
return new Squares(ctx, options);
}
var amount = 0;
this.draw = function(data){
var max_x = 0, max_y = 0, max_z = 0;
// count the number of data points
data.forEach(function(row, x){
if(x+1 > max_x) {
max_x = x+1;
}
row.forEach(function(val, y){
if(y+1 > max_y) {
max_y = y+1;
}
if(val > max_z) {
max_z = val;
}
});
});
var w = parseInt(ctx.canvas.width / max_x, 10);
var h = parseInt(ctx.canvas.height / max_y, 10);
if(w>0 && h>0) {
// draw the graph
data.forEach(function (row, x) {
row.forEach(function (val, y) {
amount = parseInt(val / max_z * 255, 10);
ctx.fillStyle = "rgb(" + parseInt(amount / 1.5, 10) + "," + parseInt(amount / 1.5, 10) + "," + amount + ")";
ctx.fillRect(x * w, y * h, w, h);
});
});
}
};
};
})(window);
How to use
html:
<canvas id="canvas" width="900" height="900"></canvas>
<script>
var data = [];
for (var i = 0; i < 10; i ++) {
if(!data[i]){
data[i] = [];
}
for (var j = 0; j < 10; j ++) {
data[i][j] = Math.random();
}
}
var ctx = document.getElementById('canvas').getContext('2d');
var sqr = new Squares(ctx, {});
sqr.draw(data);
</script>
it looks like this