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.
Related
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 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>
I working on a web app that shows a graph based on the microphone samples. I take the highest value of the vector return from getByteTimeDomainData each ~15ms and put it on a graph.
The problem is that in some phones (such as Xiaomi phones) I get fake microphone samples- it returns the same vector 10 times (~150ms), and only after that it samples the microphone again and returns a new vector for another 10 times.
Standart phones:
Highest values collected from 30 calls to getByteTimeDomainData:
230,237,237,236,236,220,220,232,232,218,218,214,214,217,217,227,227,222,222,224,224,222,222,216,216,202,202,223,223,205
Xiaomi phones:
Highest values collected from 30 calls to getByteTimeDomainData:
132,132,132,132,132,132,132,129,129,129,129,129,129,129,129,129,129,130,130,130,130,130,130,130,130,130,130,131,131,131
My question is- How can I improve it and get more samples in Xiaomi?
var start_amp = function () {
'use strict';
var soundAllowed = function (stream) {
window.persistAudioStream = stream;
var audioContext = new AudioContext();
var audioStream = audioContext.createMediaStreamSource(stream);
var analyser = audioContext.createAnalyser();
audioStream.connect(analyser);
analyser.smoothingTimeConstant = 0; // = 1 doesn't fix the samples problem
analyser.fftSize = 1024;
var frequencyArray = new Uint8Array(analyser.frequencyBinCount);
var doDraw = function () {
requestAnimationFrame(doDraw);
analyser.getByteTimeDomainData(frequencyArray);
var max = 0;
for (var i = 0; i < frequencyArray.length; i++) {
if (max < frequencyArray[i]) {
max = frequencyArray[i];
}
}
boardArray.push(max);
if (boardArray.length >= document.body.clientWidth * 0.7) {
boardArray.shift();
totalSamples += 1;
if (totalSamples == 60) {
totalSamples = 0;
}
}
draw(boardArray);
}
doDraw();
}
var soundNotAllowed = function (error) {
alert('Please check your microphone connectivity and allow this site to access it');
console.log(error);
}
navigator.getUserMedia({ audio: true }, soundAllowed, soundNotAllowed);
}
After some research, I found that creating another AudioContext and Analyser gives me different samples. Looks like there is some sample rate limit for each node.
So I created 3 AudioContext's and used the average of them:
var start_amp = function () {
'use strict';
var soundAllowed = function (stream) {
window.persistAudioStream = stream;
// Node 1
var audioContext = new AudioContext();
var audioStream = audioContext.createMediaStreamSource(stream);
var analyser = audioContext.createAnalyser();
audioStream.connect(analyser);
analyser.smoothingTimeConstant = 0; // = 1 doesn't fix the samples problem
analyser.fftSize = 1024;
// Node 2
var audioContext2 = new AudioContext();
var audioStream2 = audioContext2.createMediaStreamSource(stream);
var analyser2 = audioContext2.createAnalyser();
audioStream2.connect(analyser2);
analyser2.smoothingTimeConstant = 0; // = 1 doesn't fix the samples problem
analyser2.fftSize = 1024;
// Node 3
var audioContext3 = new AudioContext();
var audioStream3 = audioContext3.createMediaStreamSource(stream);
var analyser3 = audioContext3.createAnalyser();
audioStream3.connect(analyser3);
analyser3.smoothingTimeConstant = 0; // = 1 doesn't fix the samples problem
analyser3.fftSize = 1024;
var frequencyArray = new Uint8Array(analyser.frequencyBinCount);
var frequencyArray2 = new Uint8Array(analyser2.frequencyBinCount);
var frequencyArray3 = new Uint8Array(analyser3.frequencyBinCount);
var doDraw = function () {
requestAnimationFrame(doDraw);
analyser.getByteTimeDomainData(frequencyArray);
analyser2.getByteTimeDomainData(frequencyArray2);
analyser3.getByteTimeDomainData(frequencyArray3);
var max = 0;
for (var i = 0; i < frequencyArray.length; i++) {
if (max < frequencyArray[i]) {
max = frequencyArray[i];
}
}
var max2 = 0;
for (var i = 0; i < frequencyArray2.length; i++) {
if (max2 < frequencyArray2[i]) {
max2 = frequencyArray2[i];
}
}
var max3 = 0;
for (var i = 0; i < frequencyArray3.length; i++) {
if (max3 < frequencyArray3[i]) {
max3 = frequencyArray3[i];
}
}
boardArray.push((max + max2 + max3) / 3);
if (boardArray.length >= document.body.clientWidth * 0.7) {
boardArray.shift();
totalSamples += 1;
if (totalSamples == 60) {
totalSamples = 0;
}
}
draw(boardArray);
}
doDraw();
}
var soundNotAllowed = function (error) {
alert('Please check your microphone connectivity and allow this site to access it');
console.log(error);
}
navigator.getUserMedia({ audio: true }, soundAllowed, soundNotAllowed);
}
I'm doing a coding challenge from the coding train, and I'm trying to improve on his code. The idea is that the cars are driving around a race track. When I went back to check something, I noticed that I misspelled "activation: sigmoid", as in activation function. When I fixed it, the cars seemed to be driving in circles.
I'm a very new coder (as I am 12 years old), so many things in my code are broken, hard to understand, or just not finished. I'm also pretty new to stack overflow, so I might be breaking a lot of rules.
The link to download my project is here: https://1drv.ms/u/s!ApmY_SAko19ChzCKe5uNT7I9EZAX?e=YUg2ff
The misspelled words are at lines 29 and 34 in the nn.js file.
car.js
function pldistance(p1, p2, x, y) {
const num = abs((p2.y - p1.y) * x - (p2.x - p1.x) * y + p2.x * p1.y - p2.y * p1.x);
const den = p5.Vector.dist(p1, p2);
return num / den;
}
class Car {
constructor(brain, color = [random(255), random(255), random(255)]) {
this.colorGene = color;
this.dead = false;
this.finished = false;
this.fitness = 0;
this.rays = [];
this.wallRays = [];
this.degreeOfSight = degreeOfSight;
this.degreeOfRays = degreeOfSight / (numOfRays - 1);
if (this.degreeOfSight == 360) {
this.degreeOfRays = degreeOfSight / numOfRays;
}
this.pos = createVector(start.x, start.y);
this.vel = createVector();
this.acc = createVector();
this.sight = sight;
this.maxspeed = maxspeed;
this.maxforce = maxTurningSpeed;
this.currentGoal = 0;
this.timeTillDeadC = timeTillDead;
this.timeTillDead = this.timeTillDeadC;
this.goal;
this.rate = mutationRate;
if (degreeOfSight != 360) {
for (let a = -(this.degreeOfSight / 2); a <= this.degreeOfSight / 2; a += this.degreeOfRays) {
this.rays.push(new Ray(this.pos, radians(a)));
}
} else {
for (let a = -(this.degreeOfSight / 2); a < this.degreeOfSight / 2; a += this.degreeOfRays) {
this.rays.push(new Ray(this.pos, radians(a)));
}
}
for (let a = 0; a < 360; a += 45) {
this.wallRays.push(new Ray(this.pos, radians(a)));
}
if (brain) {
this.brain = brain.copy();
} else {
this.brain = new NeuralNetwork(this.rays.length + 2, 16, 2);
}
}
applyForce(force) {
this.acc.add(force);
}
update(x, y) {
this.timeTillDead--;
if (this.timeTillDead <= 0) {
this.dead = true;
}
if (!this.dead || this.finished) {
this.pos.add(this.vel);
this.vel.add(this.acc);
this.vel.limit(this.maxspeed);
this.acc.set(0, 0);
}
for (let i = 0; i < this.rays.length; i++) {
this.rays[i].rotate(this.vel.heading());
}
for (let i = 0; i < this.wallRays.length; i++) {
this.wallRays[i].rotate(this.vel.heading());
}
}
show(walls) {
push();
translate(this.pos.x, this.pos.y);
if (visualization) {
fill(this.colorGene[0], this.colorGene[1], this.colorGene[1]);
} else {
fill(0);
}
stroke(255);
const heading = this.vel.heading();
rotate(heading);
rectMode(CENTER);
rect(0, 0, 10, 5);
pop();
if (!this.dead) {
checkpoints[this.currentGoal].show();
}
for (let i = 0; i < this.rays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.rays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record && d < this.sight) {
record = d;
closest = pt;
}
}
}
if (closest) {
if (showLines) {
ellipse(closest.x, closest.y, 4)
stroke(255, 100)
line(this.pos.x, this.pos.y, closest.x, closest.y);
}
}
}
}
check(checkpoints, walls) {
if (!this.dead) {
this.goal = checkpoints[this.currentGoal];
const d = pldistance(this.goal.a, this.goal.b, this.pos.x, this.pos.y);
if (d < 5) {
this.fitness++;
this.currentGoal++;
this.timeTillDead = this.timeTillDeadC;
if (this.currentGoal == checkpoints.length) {
this.finished = true;
this.fitness = this.fitness * 1.5;
if (endBarrier) {
this.dead = true;
} else {
this.currentGoal = 0;
}
}
}
}
for (let i = 0; i < this.wallRays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.wallRays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record) {
record = d;
closest = pt;
}
}
}
if (record < 4) {
this.dead = true;
}
}
}
look(walls) {
const inputs = [];
for (let i = 0; i < this.wallRays.length; i++) {
let closest = null;
let record = this.sight;
for (let wall of walls) {
const pt = this.rays[i].cast(wall);
if (pt) {
const d = p5.Vector.dist(this.pos, pt);
if (d < record && d < this.sight) {
record = d;
closest = pt;
}
}
}
inputs[i] = map(record, 0, 50, 1, 0);
}
inputs.push(end.x);
inputs.push(end.y);
const output = this.brain.predict(inputs);
let angle = map(output[0], 0, 1, -PI, PI);
let speed = map(output[1], 0, 1, -this.maxspeed, this.maxspeed);
angle += this.vel.heading();
const steering = p5.Vector.fromAngle(angle);
steering.setMag(speed);
steering.limit(this.maxforce);
this.applyForce(steering);
}
mutateDemBabies() {
if (this.finished) {
this.rate = finishingMutationRate;
}
this.brain.mutate(this.rate);
let changeColor = this.brain.mutated();
if (changeColor) {
for (let color of this.colorGene) {
let r = map(random(20), 0, 20, -25, 25);
color += r;
}
}
this.rate = mutationRate;
}
dispose() {
this.brain.dispose();
}
}
nn.js
//<script src="https://cdn.jsdelivr.net/npm/#tensorflow/tfjs#1.1.0/dist/tf.min.js"></script>
class NeuralNetwork {
//this how many inputs, hidden, and output nodes there are. modelC is the brain that we want to copy to give to the new bird
constructor(inputNumber, hiddenNumber, outputNumber, modelC) {
if (modelC instanceof tf.Sequential) {
//this is the making a copy of the neural network
this.input_nodes = inputNumber;
this.hidden_nodes = hiddenNumber;
this.output_nodes = outputNumber;
this.model = modelC;
} else {
//this is the creating a random brain
this.input_nodes = inputNumber;
this.hidden_nodes = hiddenNumber;
this.output_nodes = outputNumber;
this.model = this.createBrain();
}
this.changeColor = false;
}
createBrain() {
//the model is the neural network
const model = tf.sequential();
//configuring the hidden layer
const hiddenLayer = tf.layers.dense({
units: this.hidden_nodes,
inputShape: [this.input_nodes],
activaation: "sigmoid"
});
//configuring the output layer
const outputLayer = tf.layers.dense({
units: this.output_nodes,
activaation: "sigmoid"
});
//adding the hidden layer to the model
model.add(hiddenLayer);
//adding the output layer to the model
model.add(outputLayer);
//returning the model
return model;
}
predict(inputs) {
//clearing the tensors after using them
//then returning the output
return tf.tidy(() => {
//creating a tensor with the inputs
const xs = tf.tensor2d([inputs]);
//running the inputs through the neural network
const ys = this.model.predict(xs);
//getting the raw numbers from the tensor object
const outputs = ys.dataSync();
//returning the outputs
return outputs;
});
}
copy() {
//clearing the tensors after using them
//then returning the output
return tf.tidy(() => {
//creating a new neural network
const modelCopy = this.createBrain();
//getting the weights from the old neural network
const weights = this.model.getWeights();
//setting the new weights
modelCopy.setWeights(weights);
//making a new network but this time with all the weights then returning it
return new NeuralNetwork(
this.input_nodes,
this.hidden_nodes,
this.output_nodes,
modelCopy
);
});
}
mutate(rate, colorGene) {
//clearing the tensors after using them
tf.tidy(() => {
this.changeColor = false;
//getting the weights so that we can change them later
const weights = this.model.getWeights();
//the variable that will be holding the mutated weights
const mutatedWeights = [];
for (let i = 0; i < weights.length; i++) {
//getting the shape of the current weights
let shape = weights[i].shape;
//making a copy of the raw numbers from the object tensor
//dataSync gets the numbers, but doesn't make a copy, so slice will make the copy
let values = weights[i].dataSync().slice();
for (let j = 0; j < values.length; j++) {
//if the random number is less than mutation rate the it runs the code
if (random(1) < rate) {
this.changeColor = true;
//mutating the value
//randomGaussianis returns a float from a series of numbers with a mean of 0
values[j] = values[j] + randomGaussian();
}
}
//holding the new value of each weight
mutatedWeights[i] = tf.tensor(values, shape);
}
//setting the mutated weights as the new weights
this.model.setWeights(mutatedWeights);
});
}
mutated() {
if (this.changeColor) {
this.changeColor = false;
return true;
} else {
this.changeColor = false;
return false;
}
}
dispose() {
//disposing the brain so that memory doesn't leak
this.model.dispose();
}
}
I am getting an error saying Arc is not a constructor. I have checked my code and can't understand why.
/*
Exception: Arc is not a constructor
RouteFinder/this.getPairs#Scratchpad/8:72:6
#Scratchpad/8:5:3
*/
The other answers for this error suggest that this because Arc has previously been defined, but I cant find anywhere that it is. This is a big chunk of code, so scroll down for the error - it is commented where it is
var lats = [ 51.445371, 51.45526, 51.426765, 51.441304 ]
var lons = [ -0.077581, -0.113248, -0.13091, -0.060596 ]
var finder = new RouteFinder(lats, lons);
finder.getPairs();
var nodeList = ''
for(var count = 1; count < lats.length; count++) {
nodeList += count;
}
finder.permutation("", nodeList);
finder.removeDuplicateRoutes();
var shortestRoute = finder.getShortestRoute();
var finalRouteOrder = [];
shortestRoute.nodeList.forEach(function(node) {
finalRouteOrder.push(+node);
});
alert(finalRouteOrder);
var Route = function() {
this.totalWeight = 0;
this.nodeList = [];
};
var Node = function() {
this.lat = 0;
this.lon = 0;
this.number = 0;
};
var Arc = function() {
this.startNode = new Node();
this.endNode = new Node();
this.weight = 0;
};
function reverseString(initialString) {
var reversed = '';
for(var count = initialString.length -1; count > -1; count--) {
reversed += initialString.charAt(count);
}
return reversed;
}
function calcDistance(lat1, lng1, lat2, lng2) {
var earthRadius = 6371;
var dLat = (lat2 - lat1)/180*Math.PI;
var dLng = (lng2 - lng1)/180*Math.PI;
var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(lat1/180*Math.PI) * Math.cos(lat2/180*Math.PI) * Math.sin(dLng/2) * Math.sin(dLng/2);
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
var dist = earthRadius * c;
return dist;
}
function RouteFinder(lats, lons) {
this.latArray = lats;
this.lonArray = lons;
this.orders = [];
this.arcArray = [];
this.routes = [];
this.shortestRoute;
this.numOfPoints = lats.length - 1;
this.shortestRouteLength = -1;
this.getPairs = function() {
var timesLooped = 0;
for(var count1 = 0; count1 < this.numOfPoints; count1++) {
for(var count2 = 0; count2 < (this.numOfPoints - count1); count2++) {
//I get an error here for new Arc()
this.arcArray.push(new Arc());
this.arcArray[timesLooped].startNode = {
number: count1,
};
this.arcArray[timesLooped].endNode = {
number: this.numOfPoints - count2,
};
this.arcArray[timesLooped].weight = calcDistance(this.latArray[count1], this.lonArray[count1], this.latArray[this.numOfPoints - count2], this.lonArray[this.numOfPoints - count2]);
timesLooped++;
}
}
};
this.permutation = function(prefix, str) {
var n = str.length;
if(n === 0) this.orders.push('0' + prefix + '0');
else {
for(var i = 0; i <n; i++) {
this.permutation(prefix + str.charAt(i), str.substring(0, i) + str.substring(i + 1, n));
}
}
};
this.removeDuplicateRoutes = function() {
var numberOfPermutations = this.orders.length -1;
var temp;
var size;
var toRemove = [];
for(var count1 = 0; count1 < numberOfPermutations; count1++) {
for(var count2 = 0; count2 < (numberOfPermutations - count1); count2++) {
if(this.orders[count1] == reverseString(this.orders[numberOfPermutations - count2])) {
toRemove.push(count1);
}
}
}
size = toRemove.length;
for(var count3 = 0; count3 < size; count3++) {
temp = toRemove[size - 1- count3];
var index = this.orders.indexOf(temp);
if(index > -1) {
temp.splice(index, 1);
}
}
};
this.getShortestRoute = function() {
var routesMade = 0;
for(var routeNumber = 0; routeNumber < (this.orders.length -1); routeNumber++) {
this.routes.push(new Route());
this.routes[routesMade].totalWeight = 0;
for(var count1 = 0; count1 < this.orders[routeNumber].length; count1++) {
this.routes[routesMade].nodeList.push(+this.orders[routeNumber].charAt(count1));
}
for(var count2 = 1; count2 < this.orders[routeNumber].length; count2++) {
for(var count3 = 0; count3 < this.arcArray.length; count3++) {
if(this.routes[routesMade].nodeList[count2 - 1] === this.arcArray[count3].startNode.number) {
if(this.routes[routesMade].nodeList[count2] === this.arcArray[count3].endNode.number) {
this.routes[routesMade].totalWeight += this.arcArray[count3].weight;
}
} else if (this.routes[routesMade].nodeList[count2 - 1] === this.arcArray[count3].endNode.number) {
if(this.routes[routesMade].nodeList[count2] === this.arcArray[count3].startNode.number) {
this.routes[routesMade].totalWeight += this.arcArray[count3].weight;
}
}
}
}
if(!this.shortestRoute) {
this.shortestRoute = this.routes[routesMade];
} else if(this.routes[routesMade].totalWeight < this.shortestRoute.totalWeight) {
this.shortestRoute = this.routes[routesMade];
}
routesMade++;
}
return this.shortestRoute;
};
}
I'm tearing my hair out trying to work out the problem. Help is greatly appreciated, thanks!
This
var finder = new RouteFinder(lats, lons);
gets executed before Arc variable gets its value (function). Thus at the moment of RouteFinder call that Arc variable is undefined and cannot be used as a constructor.
Update: it is about finder.getPairs(); actually where you try to call that new Arc();
You need to have Route, Node, and Arc like this:
function Route() {
this.totalWeight = 0;
this.nodeList = [];
}
function Node() {
this.lat = 0;
this.lon = 0;
this.number = 0;
}
function Arc() {
this.startNode = new Node();
this.endNode = new Node();
this.weight = 0;
}
The issue is that Arc is not yet defined. Either move your function expression assignment (var Arc = function ...) to the top of your script or convert them to function definition statements like this
function Arc() {
this.startNode = new Node();
this.endNode = new Node();
this.weight = 0;
}
Statements are hoisted and their order does not matter, but assignment to var happens in the order they appear in the file (the declaration is hoisted, but the assignment isn't).
Same goes for your Route and Node functions.
Read more about var hoisting on MDN.