Where is the sound being output? - javascript
I'm working with an NES emulator that was written in JavaScript, updating it so it runs better. I'm not knowledgeable with the Web Audio API. Can someone point me in the right direction as to where the audio is being output in the following script?
I've spent a pretty long time going through it, but can't figure it out. My goal is to bring most of the math part of the emulation into a web worker. I have already brought most of the emulator over to the web worker and improved the performance greatly. This is the part I am stuck on though.
var masterVolumeValue = 0; //0.15;
const enableAudioEmulation = true;
function SetMasterVolume(value) {
masterVolumeValue = value;
Unmute();
}
function Mute() {
if (masterVolume) masterVolume.gain.setValueAtTime(0, audio.currentTime);
}
function Unmute() {
if (masterVolume) masterVolume.gain.setValueAtTime(masterVolumeValue, audio.currentTime);
}
var audio;
var masterVolume;
var pulse1 = {
oscillator: null,
haltLengthCounter: false,
constantFlag: false,
dutyCycle: 0,
volume: 0
};
var pulse2 = {
oscillator: null,
haltLengthCounter: false,
constantFlag: false,
dutyCycle: 0,
volume: 0
};
var triangle = {};
var noise = {};
var dmc = {};
var NewApu = (function() {
var running = false;
var pulseCycles = [
[0, 1, 0, 0, 0, 0, 0, 0],
[0, 1, 1, 0, 0, 0, 0, 0],
[0, 1, 1, 1, 1, 0, 0, 0],
[1, 0, 0, 1, 1, 1, 1, 1]
];
var triangleCycle = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
var bufferSize = 1024;
var mixer;
var wave, ctx;
function initAudio() {
if (audio || !enableAudioEmulation) return;
audio = new AudioContext();
masterVolume = audio.createGain();
masterVolume.gain.setValueAtTime(masterVolumeValue, audio.currentTime);
mixer = audio.createScriptProcessor(bufferSize, 0, 1);
masterVolume.connect(audio.destination);
mixer.connect(masterVolume);
clockStep = 1789773 / 2 / audio.sampleRate; // NES clock frequency / 2 = APU sample rate
clockStep *= 0.97;
mixer.onaudioprocess = resample;
initChannel(pulse1);
initChannel(pulse2);
initChannel(triangle);
noise.lengthCounter = 0;
dmc.load = 0;
dmc.loop = false;
dmc.frequency = 0;
dmc.timer = 0;
dmc.address = 0;
dmc.bytesRemaining = 0;
dmc.currentByte = 0;
dmc.shiftPointer = 0;
dmc.length = 0;
dmc.sample = 0;
if (debug) {
wave = new Float32Array(bufferSize);
ctx = document.createElement('canvas').getContext('2d');
ctx.canvas.width = 300;
ctx.canvas.height = 50;
ctx.canvas.style.width = "300px";
output.canvas.parentNode.appendChild(ctx.canvas);
}
}
var npulse1 = {
cycle: 2,
pointer: 0,
timer: 0,
sample: 0
};
var npulse2 = {
cycle: 2,
pointer: 0,
timer: 0,
sample: 0
};
var ntriangle = {
pointer: 0,
timer: 0,
sample: 0
};
var nnoise = {
shiftRegister: 1,
timer: 0,
sample: 0
}
var counting = 0;
function updateWaveform() {
var c = ctx;
c.clearRect(0, 0, 300, 50);
c.strokeStyle = "#ffffff";
c.beginPath()
c.moveTo(0, (-wave[0] * 10) + 50);
for (var i = 1; i < wave.length; i++) {
c.lineTo((i / wave.length) * 300, (-wave[i] * 10) + 50);
}
c.stroke();
}
// More accurate, but doesn't match output rate due to emulation, so will course clicks and/or lag
var buffer = new Float32Array(bufferSize * 10);
var bufferPointer = 100;
var readPointer = 0;
var bufferLength = 0;
var bufferStep;
function resample(e) {
if (!running) return;
if (bufferLength < 400) {
//console.log('buffer underrun');
return; // extreme underflow, in case of performance issues or the tab went into the background, we just want to skip this update
}
var output = e.outputBuffer.getChannelData(0);
var l = Math.min(bufferLength, output.length);
for (var i = 0; i < l; i++) {
//output[i] = Math.tanh(buffer[readPointer] / (15*3));
output[i] = buffer[readPointer];
readPointer++;
if (readPointer == buffer.length) readPointer = 0;
}
l = output.length;
for (var i = bufferLength; i < l; i++) {
asyncTick(clockStep);
//output[i] = Math.tanh(sample / (15*3)); // 15 = sequencer "full" volume
output[i] = sample;
}
bufferLength = Math.max(0, bufferLength - output.length);
if (debug) {
for (var i = 0; i < output.length; i++) wave[i] = output[i];
updateWaveform();
}
}
// Inaccurate, and might cause precise timing issues, but prevents buffer underflow
var sample = 0; // mixed sample from all 5 channels
var clockStep = 1789773 / 2 / 48000; // NES clock frequency / 2 = APU sample rate
function clockToOutput(e) {
if (!running) return;
var output = e.outputBuffer.getChannelData(0);
var l = output.length;
for (var i = 0; i < l; i++) {
asyncTick(clockStep);
output[i] = sample / (15); // 15 = sequencer "full" volume
}
if (debug) {
for (var i = 0; i < output.length; i++) wave[i] = output[i];
updateWaveform();
}
}
// APU clock updates:
function updatePulse(channel, ref) {
if (!ref.frequency) return;
channel.timer -= click;
while (channel.timer < 0) {
channel.timer += ref.frequency;
channel.pointer = (channel.pointer + 1) & 7;
}
var duty = ref.dutyCycle;
channel.sample = ref.lengthCounter && pulseCycles[duty][channel.pointer] ? (ref.constantFlag ? ref.volume : ref.decay) : 0;
//channel.sample >>= 1;
}
function updateTriangle(channel, ref) {
if (!ref.frequency) return;
channel.timer -= click + click;
while (channel.timer < 0) {
channel.timer += ref.frequency;
channel.pointer = (channel.pointer + 1) & 31;
}
channel.sample = ref.lengthCounter && ref.linearCounter ? triangleCycle[channel.pointer] : 0;
}
function updateNoise(channel, ref) {
if (!ref.frequency) return;
channel.timer -= click;
while (channel.timer < 0) {
channel.timer += ref.frequency;
var feedback = (channel.shiftRegister & 1) ^ ((channel.shiftRegister >> ref.mode) & 1);
channel.shiftRegister >>= 1;
if (feedback) channel.shiftRegister |= 0b100000000000000;
channel.high = channel.shiftRegister & 1;
}
channel.sample = (ref.lengthCounter && channel.high) ? (ref.constantFlag ? ref.volume : ref.decay) : 0;
}
function updateDmc(channel) {
if (!channel.frequency) return;
channel.timer -= click + click;
// TODO: average multiple samples if this loops multiple times
while (channel.timer < 0) {
channel.timer += channel.frequency;
if (channel.bytesRemaining) {
if (channel.shiftPointer == 0) {
// TODO: add 4 CPU cycles
var address = channel.address + channel.length - channel.bytesRemaining;
while (address > 0xFFFF) address -= 0x8000;
channel.currentByte = cpuMemory[address];
}
var delta = (channel.currentByte >> channel.shiftPointer) & 1;
if (delta) {
if (channel.load < 126) channel.load += 2;
} else {
if (channel.load > 1) channel.load -= 2;
}
channel.shiftPointer++;
if (channel.shiftPointer == 8) {
channel.shiftPointer = 0;
channel.bytesRemaining--;
if (channel.bytesRemaining == 0 && channel.loop) channel.bytesRemaining = channel.length;
}
}
channel.sample = channel.load;
//channel.sample >>= 2;
}
}
var click = 0; // The number of accumulated APU clock cycles (½CPU). Generate sample for output buffer ever 10 clicks
var step = 0; // Counts the frame counters steps (currently only LC/Sweep clocks count up)
function asyncTick(amount) {
click = amount;
apuFrames += click;
if (apuFrames >= lcClock0 && step == 0) {
apuClockLcSw(false);
step = 1;
}
if (apuFrames >= lcClock1 && step == 1) {
apuClockLcSw(true);
step = 2;
}
if (apuFrames >= lcClock2 && step == 2) {
apuClockLcSw(false);
step = 3;
}
if (apuFrames >= lcClock3 && step == 3) {
apuClockLcSw(true);
if (enableFrameIrq) pendingFrameCount = true;
apuFrames -= lcClock2 + 1;
step = 0;
}
updatePulse(npulse1, pulse1);
updatePulse(npulse2, pulse2);
updateTriangle(ntriangle, triangle);
updateNoise(nnoise, noise);
updateDmc(dmc);
var mixPulse = 95.88 / ((8128 / (npulse1.sample + npulse2.sample)) + 100);
var mixTnd = 159.79 / (1 / ((ntriangle.sample / 8227) + (nnoise.sample / 12241) + (dmc.sample / 22638)) + 100);
sample = mixPulse + mixTnd;
//sample = (npulse1.sample + npulse2.sample);
}
function tick(amount) {
running = true;
if (bufferLength >= bufferSize * 4) return; // Avoid buffer overflow
click += amount;
if (click > clockStep) {
asyncTick(click);
buffer[bufferPointer] = sample;
//buffer[bufferPointer] = ntriangle.sample;
bufferPointer++;
bufferLength++;
if (bufferPointer == buffer.length) bufferPointer = 0;
click -= clockStep;
}
}
function initChannel(channel) {
channel.volume = 0;
channel.lengthCounter = 0;
channel.sweepTimer = 0;
channel.linearReload = false;
}
var lengthCounterValues = [10, 254, 20, 2, 40, 4, 80, 6, 160, 8, 60, 10, 14, 12, 26, 14, 12, 16, 24, 18, 48, 20, 96, 22, 192, 24, 72, 26, 16, 28, 32, 30];
var noisePeriodValues = [4, 8, 16, 32, 64, 96, 128, 160, 202, 254, 380, 508, 762, 1016, 2034, 4068];
var dmcTimerValues = [428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54];
var apuFrames = 0;
var lcClock0 = 3728;
var lcClock1 = 7456;
var lcClock2 = 11185;
var lcClock3 = 14914;
var enableFrameIrq = false;
var frameCounterMode = false;
function apuClockLcSw(updateSweeps) {
apuFrameLinearCounter();
if (!updateSweeps) return;
apuFrameSweepChannel(pulse1);
apuFrameSweepChannel(pulse2);
apuFrameSweepChannel(triangle);
apuFrameSweepChannel(noise);
}
function apuFrameSweepChannel(channel) {
if (channel.sweepEnabled) {
if (channel.sweepTimer <= 0) {
channel.sweepTimer += channel.sweepPeriod + 1;
var amount = ((channel.frequency >> channel.sweepShift));
if (channel.sweepNegate) amount = -amount - 1;
var target = (channel.frequency + amount) & 0xfff;
if (target < 0x800 && channel.frequency > 7) channel.frequency = target;
}
channel.sweepTimer -= 1;
}
if (!channel.haltLengthCounter) channel.lengthCounter = Math.max(0, channel.lengthCounter - 1);
}
function apuFrameLinearCounter() {
if (triangle.linearReload) {
triangle.linearCounter = triangle.linearReloadValue;
} else {
triangle.linearCounter = Math.max(0, triangle.linearCounter - 1);
}
if (!triangle.haltLengthCounter) triangle.linearReload = false;
clockDivider(pulse1);
clockDivider(pulse2);
clockDivider(noise);
}
function clockDivider(channel) {
if (channel.startFlag) {
channel.startFlag = false;
channel.decay = 15;
channel.divider = channel.volume;
} else {
if (channel.divider) {
channel.divider--;
return;
}
if (channel.decay) {
channel.divider = channel.volume;
channel.decay--;
return;
}
if (channel.haltLengthCounter) channel.decay = 15; // HLC flag is the same as the Decay loop flag
}
}
function dutyCycle(channel, value) {
channel.haltLengthCounter = ((value & 0x20) != 0);
channel.constantFlag = ((value & 0x10) != 0);
channel.dutyCycle = ((value & 0xC0) >> 6);
channel.volume = (value & 0x0F);
//if (!channel.started) channel.oscillator.start();
channel.started = true;
}
function triangleHalt(channel, value) {
channel.haltLengthCounter = ((value & 0x80) != 0);
channel.linearReloadValue = value & 0x7f;
}
function noisePeriod(value) {
var period = value & 0x0F;
noise.mode = (value & 0x80) ? 6 : 1;
noise.frequency = noisePeriodValues[period];
//noise.oscillator.playbackRate.setValueAtTime(1 / (1 << ((period)/6)), audio.currentTime);
}
function noiseLength(value) {
noise.lengthCounter = lengthCounterValues[(value & 0xf8) >> 3];
noise.startFlag = true;
}
function timer(channel, value) {
channel.frequency &= 0xf00;
channel.frequency |= value;
}
function length(channel, value) {
channel.frequency &= 0x0ff;
channel.frequency |= (value & 0x07) << 8;
channel.lengthCounter = lengthCounterValues[(value & 0xf8) >> 3];
channel.linearReload = true;
channel.startFlag = true;
}
function sweep(channel, value) {
channel.sweepEnabled = ((value & 0x80) != 0);
channel.sweepPeriod = (value & 0x70) >> 4;
channel.sweepTimer = channel.sweepPeriod + 1;
channel.sweepNegate = ((value & 0x08) != 0);
channel.sweepShift = (value & 0x07);
}
function frameCounter(value) {
//enableFrameIrq = (value & 0x40) == 0;
frameCounterMode = (value & 0x80) != 0;
lcClock3 = frameCounterMode ? 18640 : 14914;
if (frameCounterMode) resetStep();
}
function statusRead() {
//enableFrameIrq = true;
return (pulse1.lengthCounter == 0 ? 0 : 0x01) |
(pulse2.lengthCounter == 0 ? 0 : 0x02) |
(triangle.lengthCounter == 0 ? 0 : 0x04) |
(noise.lengthCounter == 0 ? 0 : 0x08) |
(dmc.bytesRemaining ? 0x10 : 0);
}
function statusWrite(value) {
if ((value & 0x01) == 0) pulse1.lengthCounter = 0;
if ((value & 0x02) == 0) pulse2.lengthCounter = 0;
if ((value & 0x04) == 0) triangle.lengthCounter = 0;
if ((value & 0x08) == 0) noise.lengthCounter = 0;
if (value & 0x10) {
if (dmc.bytesRemaining == 0) {
dmc.bytesRemaining = dmc.length;
dmc.shiftPointer = 0;
}
} else {
dmc.bytesRemaining = 0;
}
}
function dmcControl(value) {
dmc.irq = (value & 0x80) != 0;
dmc.loop = (value & 0x40) != 0;
dmc.frequency = dmcTimerValues[value & 0xf];
}
function dmcLoad(value) {
dmc.load = value & 0x7f;
}
function dmcAddress(value) {
dmc.address = 0xC000 | (value << 6);
}
function dmcLength(value) {
dmc.length = (value << 4) | 1;
}
function setApuRegisters() {
if (enableAudioEmulation) {
hwRegisters[0x4000] = new HwRegister(null, function(val) { dutyCycle(pulse1, val); });
hwRegisters[0x4004] = new HwRegister(null, function(val) { dutyCycle(pulse2, val); });
hwRegisters[0x400c] = new HwRegister(null, function(val) { dutyCycle(noise, val); });
hwRegisters[0x4001] = new HwRegister(null, function(val) { sweep(pulse1, val); });
hwRegisters[0x4005] = new HwRegister(null, function(val) { sweep(pulse2, val); });
hwRegisters[0x4002] = new HwRegister(null, function(val) { timer(pulse1, val); });
hwRegisters[0x4006] = new HwRegister(null, function(val) { timer(pulse2, val); });
hwRegisters[0x400A] = new HwRegister(null, function(val) { timer(triangle, val); });
hwRegisters[0x400E] = new HwRegister(null, noisePeriod);
hwRegisters[0x4003] = new HwRegister(null, function(val) { length(pulse1, val); });
hwRegisters[0x4007] = new HwRegister(null, function(val) { length(pulse2, val); });
hwRegisters[0x400B] = new HwRegister(null, function(val) { length(triangle, val); });
hwRegisters[0x400F] = new HwRegister(null, noiseLength);
hwRegisters[0x4008] = new HwRegister(null, function(val) { triangleHalt(triangle, val); });
hwRegisters[0x4010] = new HwRegister(null, dmcControl);
hwRegisters[0x4011] = new HwRegister(null, dmcLoad);
hwRegisters[0x4012] = new HwRegister(null, dmcAddress);
hwRegisters[0x4013] = new HwRegister(null, dmcLength);
}
hwRegisters[0x4015] = new HwRegister(statusRead, statusWrite);
hwRegisters[0x4017].write = frameCounter;
}
function resetStep() {
step = 0;
apuFrames = 0;
}
var apuInterface = {
tick: tick, // function() { running = true; },
init: initAudio,
setRegisters: setApuRegisters
};
return apuInterface;
});
apu = NewApu();
Related
Detecting if something in a position already exists
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]); }
how to generate random points in a circle (fractal trees)
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!
How to fix similar predictions in tenserflow.js
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(); } }
How to change voice pitch without noise using web audio javascript?
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.
Extract images from a single PDF page using PDFJS
Is there a way to extract images from a single PDF page using PDFJS framework? After some googling, I found this discussion talking about that, but unfortunately its not very specific, he talks about passing an imageLayer to the CanvasGraphics object, but I didn't recognize what he does mean by that.
let imgIndex = 1; let opList = await page.getOperatorList(); for (let k = 0; k < opList.fnArray.length; k++) { if (opList.fnArray[k] == pdfjsLib.OPS.paintJpegXObject || opList.fnArray[k] == pdfjsLib.OPS.paintImageXObject) { function getImage() { return new Promise(async function (res, rej) { let img = null try { //-------either get data from page.objs.get img = page.objs.get(opList.argsArray[k][0]) } catch (err) { if (opList.argsArray[k][0].startsWith("g_")) { //---------or get data from page.commonObjs.get img = page.commonObjs.get(opList.argsArray[k][0]) } else { res() } } try { //------------------ directly creating image data from returned array does not print proper image also sometimes throw error. // var idata = new ImageData(img.data, img.width); var canvas = document.createElement('canvas'); canvas.width = img.width; canvas.height = img.height; var ctx = canvas.getContext('2d'); //------------------- below function will process data and print proper image on provided canvas context. kind parameter in returned data is used in this function to prcess the data putBinaryImageData(ctx, img) // ctx.putImageData(idata, 0, 0); function getCanvasBlob(canvas) { return new Promise(function (resolve, reject) { canvas.toBlob(function (blob) { resolve(blob) }) }) } getCanvasBlob(canvas).then((blob) => { zip.folder('images').file(`page-${i}-image-${imgIndex}`, blob, { base64: false }); imgIndex++; res() }).catch((err) => { // console.log(err) res() }) } catch (err) { console.log(err) res() } }) } await getImage() } } IMP ----------- Below function is taken from source code of PDF.js to convert image data to proper images as the data returned from pdf.js inbuilt function for images is uint8clampedarray which can not be used as it is for images, this data needs to be processed which is being done using below function. export function putBinaryImageData(ctx, imgData, transferMaps = null) { const FULL_CHUNK_HEIGHT = 16; const ImageKind = { GRAYSCALE_1BPP: 1, RGB_24BPP: 2, RGBA_32BPP: 3 }; if (typeof ImageData !== "undefined" && imgData instanceof ImageData) { ctx.putImageData(imgData, 0, 0); return; } const height = imgData.height, width = imgData.width; const partialChunkHeight = height % FULL_CHUNK_HEIGHT; const fullChunks = (height - partialChunkHeight) / FULL_CHUNK_HEIGHT; const totalChunks = partialChunkHeight === 0 ? fullChunks : fullChunks + 1; const chunkImgData = ctx.createImageData(width, FULL_CHUNK_HEIGHT); let srcPos = 0, destPos; const src = imgData.data; const dest = chunkImgData.data; let i, j, thisChunkHeight, elemsInThisChunk; let transferMapRed, transferMapGreen, transferMapBlue, transferMapGray; if (transferMaps) { switch (transferMaps.length) { case 1: transferMapRed = transferMaps[0]; transferMapGreen = transferMaps[0]; transferMapBlue = transferMaps[0]; transferMapGray = transferMaps[0]; break; case 4: transferMapRed = transferMaps[0]; transferMapGreen = transferMaps[1]; transferMapBlue = transferMaps[2]; transferMapGray = transferMaps[3]; break; } } if (imgData.kind === ImageKind.GRAYSCALE_1BPP) { const srcLength = src.byteLength; const dest32 = new Uint32Array(dest.buffer, 0, dest.byteLength >> 2); const dest32DataLength = dest32.length; const fullSrcDiff = width + 7 >> 3; let white = 0xffffffff; let black = _util.IsLittleEndianCached.value ? 0xff000000 : 0x000000ff; if (transferMapGray) { if (transferMapGray[0] === 0xff && transferMapGray[0xff] === 0) { [white, black] = [black, white]; } } for (i = 0; i < totalChunks; i++) { thisChunkHeight = i < fullChunks ? FULL_CHUNK_HEIGHT : partialChunkHeight; destPos = 0; for (j = 0; j < thisChunkHeight; j++) { const srcDiff = srcLength - srcPos; let k = 0; const kEnd = srcDiff > fullSrcDiff ? width : srcDiff * 8 - 7; const kEndUnrolled = kEnd & ~7; let mask = 0; let srcByte = 0; for (; k < kEndUnrolled; k += 8) { srcByte = src[srcPos++]; dest32[destPos++] = srcByte & 128 ? white : black; dest32[destPos++] = srcByte & 64 ? white : black; dest32[destPos++] = srcByte & 32 ? white : black; dest32[destPos++] = srcByte & 16 ? white : black; dest32[destPos++] = srcByte & 8 ? white : black; dest32[destPos++] = srcByte & 4 ? white : black; dest32[destPos++] = srcByte & 2 ? white : black; dest32[destPos++] = srcByte & 1 ? white : black; } for (; k < kEnd; k++) { if (mask === 0) { srcByte = src[srcPos++]; mask = 128; } dest32[destPos++] = srcByte & mask ? white : black; mask >>= 1; } } while (destPos < dest32DataLength) { dest32[destPos++] = 0; } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else if (imgData.kind === ImageKind.RGBA_32BPP) { const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue); j = 0; elemsInThisChunk = width * FULL_CHUNK_HEIGHT * 4; for (i = 0; i < fullChunks; i++) { dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); srcPos += elemsInThisChunk; if (hasTransferMaps) { for (let k = 0; k < elemsInThisChunk; k += 4) { if (transferMapRed) { dest[k + 0] = transferMapRed[dest[k + 0]]; } if (transferMapGreen) { dest[k + 1] = transferMapGreen[dest[k + 1]]; } if (transferMapBlue) { dest[k + 2] = transferMapBlue[dest[k + 2]]; } } } ctx.putImageData(chunkImgData, 0, j); j += FULL_CHUNK_HEIGHT; } if (i < totalChunks) { elemsInThisChunk = width * partialChunkHeight * 4; dest.set(src.subarray(srcPos, srcPos + elemsInThisChunk)); if (hasTransferMaps) { for (let k = 0; k < elemsInThisChunk; k += 4) { if (transferMapRed) { dest[k + 0] = transferMapRed[dest[k + 0]]; } if (transferMapGreen) { dest[k + 1] = transferMapGreen[dest[k + 1]]; } if (transferMapBlue) { dest[k + 2] = transferMapBlue[dest[k + 2]]; } } } ctx.putImageData(chunkImgData, 0, j); } } else if (imgData.kind === ImageKind.RGB_24BPP) { const hasTransferMaps = !!(transferMapRed || transferMapGreen || transferMapBlue); thisChunkHeight = FULL_CHUNK_HEIGHT; elemsInThisChunk = width * thisChunkHeight; for (i = 0; i < totalChunks; i++) { if (i >= fullChunks) { thisChunkHeight = partialChunkHeight; elemsInThisChunk = width * thisChunkHeight; } destPos = 0; for (j = elemsInThisChunk; j--;) { dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = src[srcPos++]; dest[destPos++] = 255; } if (hasTransferMaps) { for (let k = 0; k < destPos; k += 4) { if (transferMapRed) { dest[k + 0] = transferMapRed[dest[k + 0]]; } if (transferMapGreen) { dest[k + 1] = transferMapGreen[dest[k + 1]]; } if (transferMapBlue) { dest[k + 2] = transferMapBlue[dest[k + 2]]; } } } ctx.putImageData(chunkImgData, 0, i * FULL_CHUNK_HEIGHT); } } else { throw new Error(`bad image kind: ${imgData.kind}`); } }