Web Audio Api writing oscilator with createBuffer() - javascript

I am trying to make a sine wave with createBuffer(). Like following code.
var TWOPI = Math.PI * 2;
var Sinewave = function(freq, amp, phase){
var self = this;
this.src;
this.init = function(){
var osc = new self.Osc(phase);
var buffer = ac.createBuffer(1, osc.samplerate, osc.samplerate);
var buffering = buffer.getChannelData(0);
for(i = 0; i < buffering.length; i++){
buffering[i] = amp * this.sinetick(osc, freq);
}
this.src = ac.createBufferSource();
this.src.buffer = buffer;
this.src.loop = true;
this.src.connect(contect.destination);
}
this.Osc = function(_phase){
this.samplerate = context.sampleRate;
this.twopiovsr = TWOPI/this.samplerate;
this.curfreq = 0;
this.curphase = _phase;
this.incr = 0.0;
}
this.sinetick = function(osc, freq){
var val = Math.sin(osc.curphase);
if(osc.curfreq != freq){
osc.curfreq = freq;
osc.incr = osc.twopiovsr * freq;
}
osc.curphase += osc.incr;
if(osc.curphase >= TWOPI){
osc.curphase -= TWOPI;
}
if(osc.curphase < 0.0){
osc.curphase += TWOPI;
}
return val;
}
this.play = function(duration){
this.src.start(0);
this.src.stop(duration);
}
}
This only contains function for sine wave but I've succeeded to implement other shapes as well.
Reason for this in the first place was to set phase offset, if there's a better option please give me a notice.
So, When I look at the output signal through oscilloscope they look like how they should be. However when I try to modulate frequency of a regular createOscillator node with the signal created by code above, output is not pretty. When regular createOscillators nodes fm themselves, waves on the oscilloscope changes horizontally, only frequencies, but with the signal generated with the code above gives changes in both horizontal and vertical giving clips
what could be the problem? Thanks

And the problem was
var buffer = ac.createBuffer(1, osc.samplerate, osc.samplerate);
which should be
var buffer = ac.createBuffer(1, osc.samplerate/2, osc.samplerate);
Thanks guys

Related

Webaudio panner crackling sound when position is changed

I'm working on a 3D web app where I want to use positional 3D audio.
I started noticing that a crackling noise appears on the output as the sound source changes its position.
Initially I thought it could be a programming issue or a library issue (I was using howler.js).
I made a very basic example based on plain JS and Webaudio API which is shown here
let params={
"xPosition":0,
"zPosition":-1
};
let gui=new dat.GUI( { autoPlace: true, width: 500 });
const AudioContext = window.AudioContext || window.webkitAudioContext;
let audioCtx;
let panner;
let listener;
let source;
let osc;
function initWebAudio(){
audioCtx = new AudioContext();
panner = audioCtx.createPanner();
listener = audioCtx.listener;
osc = audioCtx.createOscillator();
osc.frequency.value = 70;
osc.connect(panner);
osc.start(0);
panner.connect(audioCtx.destination);
panner.panningModel = 'HRTF';
panner.distanceModel = 'linear';
panner.maxDistance = 60;
panner.refDistance = 1;
panner.rolloffFactor = 1;
panner.coneInnerAngle = 360;
panner.coneOuterAngle = 360;
panner.coneOuterGain = 0;
panner.positionX.setValueAtTime(0,audioCtx.currentTime);
panner.positionY.setValueAtTime(1,audioCtx.currentTime);
panner.positionZ.setValueAtTime(1,audioCtx.currentTime);
if(panner.orientationX) {
panner.orientationX.value = 1;
panner.orientationY.value = 0;
panner.orientationZ.value = 0;
} else {
panner.setOrientation(1,0,0);
}
if(listener.forwardX) {
listener.forwardX.value = 0;
listener.forwardY.value = 0;
listener.forwardZ.value = -1;
listener.upX.value = 0;
listener.upY.value = 1;
listener.upZ.value = 0;
} else {
listener.setOrientation(0,0,-1,0,1,0);
}
if(listener.positionX) {
listener.positionX.value = 0;
listener.positionY.value = 0;
listener.positionZ.value = 0;
} else {
listener.setPosition(0,0,0);
}
}
function positionPanner() {
if(panner.positionX) {
panner.positionX.setValueAtTime(params.xPosition, audioCtx.currentTime);
} else {
panner.setPosition(params.xPosition,0,params.zPosition);
}
}
function tick(){
positionPanner();
}
function onClickStart(){
initWebAudio();
gui.add(osc.frequency,"value",50,220).name("frequency");
setInterval(tick,50);
}
function buildMenu(){
gui.add(params,"xPosition",-3,3).step(0.001);
gui.add(window,"onClickStart").name("start");
}
buildMenu();
https://jsfiddle.net/fedeM75/t9vpm8so/23/
Press start, then as you change the xPosition slider a crackling sound appears.
It is specially noticeable using headphones.
I searched on google and some people say that it has to do with the rate of change of the position. But I tried with different value ranges and it still happens with small changes.
By the way if the condition to use the panner is to have a very slow rate of change in the position it does not seem to be useful in real world cases.
I use a timer to update the position but the same happens using requestAnimationFrame()
Does anyone has a clue on why this happens and how to solve it?
I had this crackling audio with a simple GainNode. It could be a bug indeed since a simple implementation in the ScriptProcessor does work. Or it's the fact that I/we just don't get how the timing with audioCtx.currentTime should be handled?
For me the solution was to ditch the gain node and use a ScriptProcessor (deprecated: you could/should use an AudioWorkletNode) with my own gain applied to the audio signal directly:
let myGain = 1.0; // change this as you like without crackling noises
let whiteNoise = audioCtx.createScriptProcessor(4096, 0, 1);
whiteNoise.onaudioprocess = function(e) {
let output = e.outputBuffer.getChannelData(0);
for (let i = 0; i < output.length; i++) {
output[i] = (Math.random() * 2 - 1) * myGain;
}
}
This won't fix your problem directly since you have this problem with the 3D panner, but perhaps you can find sourcecode/examples for such a panner implementation and port it to an AudioWorkletNode? Hope this helps.

My javascript canvas map script and poor performance

Basically below is my script for a prototype which uses 128x128 tiles to draw a map on a canvas which user can drag to move around.
Script does work. However I have a few problems to be solved:
1. Poor performance and I can't figure out why.
2. I am missing a method to buffer the tiles before the actual drawing.
3. If you notice any other issues also that could help me to make things run more smoothly it would be fantastic.
Some explanations for the script:
variables
coordinates - Defines the actual images to be displayed. Image file names are type of '0_1.jpg', where 0 is Y and 1 is X.
mouse_position - As name says, is keeping record of mouse position.
position - This is a poorly named variable. It defines the position of the context drawn on canvas. This changes when user drags the view.
Any assistance would be appreciated greatly. Thank you.
var coordinates = [0, 0];
var mouse_position = [0, 0];
var position = [-128, -128];
var canvas = document.getElementById('map_canvas');
var context = canvas.getContext('2d');
var buffer = [];
var buffer_x = Math.floor(window.innerWidth/128)+4;
var buffer_y = Math.floor(window.innerHeight/128)+4;
var animation_frame_request = function() {
var a = window.requestAnimationFrame;
var b = window.webkitRequestAnimationFrame;
var c = window.mozRequestAnimationFrame;
var d = function(callback) {
window.setTimeout(callback, 1000/60);
}
return a || b || c || d;
}
var resizeCanvas = function() {
window.canvas.width = window.innerWidth;
window.canvas.height = window.innerHeight;
window.buffer_x = Math.floor(window.innerWidth/128)+4;
window.buffer_y = Math.floor(window.innerHeight/128)+4;
window.buffer = [];
for (row = 0; row < window.buffer_y; row++) {
x = [];
for (col = 0; col < window.buffer_x; col++) {
x.push(new Image());
}
window.buffer.push(x);
}
}
var render = function() {
animation_frame_request(render);
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
cy = window.coordinates[1]+row;
cx = window.coordinates[0]+col;
window.buffer[row][col].src = 'map/'+cy+'_'+cx+'.jpg';
}
}
for (row = 0; row < window.buffer_y; row++) {
for (col = 0; col < window.buffer_x; col++) {
window.context.drawImage(window.buffer[row][col],
window.position[0]+col*128,
window.position[1]+row*128, 128, 128);
}
}
}
var events = function() {
window.canvas.onmousemove = function(e) {
if (e['buttons'] == 1) {
window.position[0] += (e.clientX-window.mouse_position[0]);
window.position[1] += (e.clientY-window.mouse_position[1]);
if (window.position[0] >= 0) {
window.position[0] = -128;
window.coordinates[0] -= 1;
} else if (window.position[0] < -128) {
window.position[0] = 0;
window.coordinates[0] += 1;
}
if (window.position[1] >= 0) {
window.position[1] = -128;
window.coordinates[1] -= 1;
} else if (window.position[1] < -128) {
window.position[1] = 0;
window.coordinates[1] += 1;
}
render();
}
window.mouse_position[0] = e.clientX;
window.mouse_position[1] = e.clientY;
}
}
window.addEventListener('resize', resizeCanvas, false);
window.addEventListener('load', resizeCanvas, false);
window.addEventListener('mousemove', events, false);
resizeCanvas();
To get better performance you should avoid changing the src of img nodes and move them around instead.
A simple way to minimize the number of img nodes handled and modified (except for screen positioning) is to use an LRU (Least Recently Used) cache.
Basically you keep a cache of last say 100 image nodes (they must be enough to cover at least one screen) by using a dictionary mapping the src url to a node object and also keeping them all in a doubly-linked list.
When a tile is required you first check in the cache, and if it's already there just move it to the front of LRU list and move the img coordinates, otherwise create a new node and set the source or, if you already hit the cache limit, reuse the last node in the doubly-linked list instead. In code:
function setTile(x, y, src) {
var t = cache[src];
if (!t) {
if (cache_count == MAXCACHE) {
t = lru_last;
t.prev.next = null;
lru_last = t.prev;
t.prev = t.next = null;
delete cache[t.src]
t.src = src;
t.img.src = src;
cache[t.src] = t;
} else {
t = { prev: null,
next: null,
img: document.createElement("img") };
t.src = src;
t.img.src = src;
t.img.className = "tile";
scr.appendChild(t.img);
cache[t.src] = t;
cache_count += 1;
}
} else {
if (t.prev) t.prev.next = t.next; else lru_first = t.next;
if (t.next) t.next.prev = t.prev; else lru_last = t.prev;
}
t.prev = null; t.next = lru_first;
if (t.next) t.next.prev = t; else lru_last = t;
lru_first = t;
t.img.style.left = x + "px";
t.img.style.top = y + "px";
scr.appendChild(t.img);
}
I'm also always appending the requested tile to the container so that it goes in front of all other existing tiles; this way I don't need to remove old tiles and they're simply left behind.
To update the screen I just iterate over all the tiles I need and request them:
function setView(x0, y0) {
var w = scr.offsetWidth;
var h = scr.offsetHeight;
var iy0 = y0 >> 7;
var ix0 = x0 >> 7;
for (var y=iy0; y*128 < y0+h; y++) {
for (var x=ix0; x*128 < x0+w; x++) {
setTile(x*128-x0, y*128-y0, "tile_" + y + "_" + x + ".jpg");
}
}
}
most of the time the setTile request will just update the x and y coordinates of an existing img tag, without changing anything else. At the same time no more than MAXCACHE image nodes will be present on the screen.
You can see a full working example in
http://raksy.dyndns.org/tiles/tiles.html

For Loop MovieClip Grid not showing on stage

So I'm a newbie and should obviously spend time in the tuts, but I'm looking for a quick answer. Basically, I've created a grid of movie clips with AS3. When I 'preview' the flash (as a flash or HTML) it shows up fine. Success. Yet, the stage remains empty.
Q1) Will the stage remain empty as I have used AS3 to dynamically 'draw' the grid of mc's? Or is there a slit of code I am missing to make this baby show up on the stage?
Q2) I've managed to use alpha to make the MC's 'fade' on hover - but I want to make them change color (to red) when hovered over. I've searched everywhere and can't seem to find the right script.
Here is my code:
var stage = new createjs.Stage("canvas");
var image = new createjs.Bitmap("images/square.png");
stage.addChild(image);
createjs.Ticker.addEventListener("tick", handleTick);
function handleTick(event) {
image.x += 10;
stage.update();
}
var x0:Number = 0;
var y0:Number = 0;
var nt:Number = 72;
var nc = 10;
var vd:Number = 12;
var hd:Number = 12;
for (var i = 1; i <= nt; i++) {
var mc = this.attachMovie("square", "square" + i, i);
var aprox = Math.floor((i - 1) / nc);
mc._x = x0 + hd * ((i - aprox * nc) - 1);
mc._y = y0 + aprox * vd;
mc.useHandCursor = true;
// fade in
mc.onRollOver = function()
{
this.onEnterFrame = function()
{
if (this._alpha > 0) {
this._alpha -= 10;
} else {
this._alpha = 0;
delete this.onEnterFrame;
}
};
};
// fade out
mc.onRollOut = function()
{
this.onEnterFrame = function()
{
if (this._alpha < 100) {
this._alpha += 10;
} else {
this._alpha = 100;
delete this.onEnterFrame;
}
};
};
}
Thanks in advance - sorry I am a noob.
This will never work. 1/3 of your code is in AS3, 2/3 in AS2. Considering you haven't been thrown any error, I assume you exported it as AS2.

Multiple different Collada scenes with Three.js animation won't work

I have a multiple models in Collada format (knight.dae & archer.dae).
My problem is that i can't get them all to animate(lets say Idle with is 2-3frames).
When i load the scene i either get only one animated model and one stil model(no animation,no nothing,it's like he's being modeled in 3ds max).
I know my problem is with the skin and morphs but i searched alot and didnt find an answer and due to my lack of experience my attempts have failed so far.
Help pls!
//animation length of the model is 150(and it hosts 4 different animations)
var startFrame = 0, endFrame = 150, totalFrames = endFrame - startFrame, lastFrame;
var urls = [];
var characters = [];
urls.push('3D/archer/archer.dae');
urls.push('3D/archer/archer.dae');
//here's the loader
loader = new THREE.ColladaLoader();
loader.options.convertUpAxis = true;
for (var i=0;i<urls.length;i++) {
loader.load(urls[i],function colladaReady( collada ){
player = collada.scene;
player.scale.x = player.scale.y = player.scale.z =10;
player.position.y=115;
player.position.z=i*200;
player.updateMatrix()
skin = collada.skins [ 0 ];
//skinArray.push(skin);;
var mesh=new THREE.Mesh(new THREE.CubeGeometry(10,20,10,1,1,1));
player.add(mesh);
characters.push(mesh);
scene.add( player );
});
}
//i added the cube because i use raycaster and it doesnt detect collada obj
// Here is where i try my animation.
function animate() {
requestAnimationFrame( animate );
render();
}
function render() {
update();
renderer.render(scene,camera);
}
function update() {
var delta = clock.getDelta();
delta = delta / 2;
if ( t > 1 ) t = 0;
if ( skin )
{
skin.morphTargetInfluences[lastFrame] = 0;
var currentFrame = startFrame + Math.floor(t*totalFrames);
skin.morphTargetInfluences[currentFrame] = 1;
t += delta;
lastFrame = currentFrame;
}
}
Try something like this.... at the beginning:
var skins = [];
At your collada callback, something you already seem to have thought about:
skins.push(collada.skins[0]);
At your renderer, instead of the current if (skin) clause:
t += delta;
lastFrame = currentFrame;
var currentFrame = startFrame + Math.floor(t*totalFrames);
for (var i = 0; i < skins.length; i++) {
var skin = skins[i];
if (skin) {
skin.morphTargetInfluences[lastFrame] = 0;
skin.morphTargetInfluences[currentFrame] = 1;
}
}
Point being, you need to loop all the skins in the update() function. I didn't check the frame handling code very carefully as that was not the question.. If your skins have different amount of frames you need to take those into account in your code (maybe make the lastFrame, currentFrame etc variables to arrays matching the skins array).
if ( skinArray[0] && skinArray[1] )
{
skinArray[0].morphTargetInfluences[lastFrame] = 0;
skinArray[1].morphTargetInfluences[lastFrame] = 0;
var currentFrame = startFrame + Math.floor(t*totalFrames);
skinArray[0].morphTargetInfluences[currentFrame] = 1;
skinArray[1].morphTargetInfluences[currentFrame] = 1;
t += delta;
lastFrame = currentFrame;
}
I came up with this code,it does the job but i just don't like it,mainly because it feels it's hardcoded.So if any of you guys can come up with a more elegant solution i'd be more then happy.

ipad safari stops running code events (canvases with 1 image object) out of memory

(Yeah I'm from sweden so my english might not be perfect ;)
I have troubles with memory on the ipad. This code does not crash the broser, but it just stops. At some point it never goes in to any of the event handlers on the Image object. I have no clue why..? I have searched the forum and googled for a couple of days about workarounds. But they don't really fit what I am trying to achieve(?). (Because I only have 1 Image object).
What I have created is as follows:
1 main canvas which is visible to the user.
16 other canvases which I draw on.
1 Image Object which I load images with.
all images are pngs with alpha and have the following dimension 900x373 px. All the canvases have the same dimensions.
On the 16 other canvases there are drawn 8 images.
The purpose of this is to be able to rotate an object which has interchangable layers.
(1 angle is an object from a different angle, so it will look like it's rotating when the main loop runs.) The rotation is supposed to be controlled by touch/mouse but this should demonstrate what I want to achieve.
I know that this is a lot of code to look through and it might not be the best written code either since I'm a novice at javascript. However it does work fine on my laptop in chrome.
I've read something about events holding references to imageObjects and therefore they would not get GC'd. But does that imply here when I only have one Image Object ?
I also tried adding and removing the listeners with jquery syntax but no success.
It would be much appreciated if anyone would take the time to answer this.
Regards Oscar.
initialization of variables:
var drawingCanvas = null;
var context = null;
var numAngles = 8;
var angleStep = 32/ numAngles;
var canvasAngles = [];
var loadStatus = {};
var basePath = "assets_900/";
var layerPaths = [];
var layerPathsA = ["black/","v16/","f86/","fA00049/","fA00340/","fTG02/","fTG02/","fTJ02/"];
var layerPathsB = ["red/","v16/","f86/","fA00049/","fA00340/","fTG02/","fTG02/","fTJ02/"];
var layerPathsC = ["black/","v16/","f86/","fR134/","fA00340/","fTG02/","fTG02/","fTJ02/"];
var layerPathsD = ["red/","v16/","f86/","fR134/","fA00340/","fTG02/","fTG02/","fTJ02/"];
var layerPathsArr = [layerPathsA,layerPathsB,layerPathsC,layerPathsD];
layerPathsCounter = 0;
var numLayers = layerPaths.length;
var imageInit = null;
var SW = 900; //1920
var SH = 373; //796
var loopcounter = 0;
first setup of canvases and other stuf:
layerPaths = layerPathsArr[0];
drawingCanvas = document.getElementById('myDrawing');
context = drawingCanvas.getContext('2d');
for(var i = 0; i < numAngles; i++){
var canvas = document.createElement('canvas');
var canvasContext = canvas.getContext('2d');
canvasContext.createImageData(SW, SH);
canvas.height = SH;
canvas.width = SW;
canvasAngles.push(canvas);
}
this will init the loop, and then it will never stop, it will jsut continue until mobile safari crashes:
loadImage(0,0,0);
this is a loop that loads images:
when it has loaded 8 images it draws them on one of the 16 canvases and then that canvas gets drawn on the visible canvas. then it loads a new angle and 8 new images , and so on...
function loadImage(pathIndex,layerIndex,angleIndex){
if(layerIndex < layerPaths.length ){
//logger.log("path :" + pathIndex +" lajr : "+ layerIndex + " angl: " + angleIndex);
imageInit = new Image();
imageInit.onload = function(){
var canvas = canvasAngles[angleIndex];
var canvasContext = canvas.getContext('2d');
canvasContext.drawImage(imageInit,0, 0);
imageInit.onload = null;
imageInit.onerror = null;
imageInit.onabort = null;
imageInit.src = "data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==";
imageInit = null;
delete imageInit;
loadImage(pathIndex,layerIndex+1,angleIndex);
}
imageInit.onerror = function(){
logger.log("Error loading, retrying....");
loadImage(pathIndex,layerIndex,angleIndex);
}
imageInit.onabort = function(){
logger.log("Error loading (aborted)");
}
var path = "";
if(pathIndex < 10){
path = basePath + layerPaths[layerIndex] + "img000"+ pathIndex + ".png";
}else{
path = basePath + layerPaths[layerIndex] + "img00"+ pathIndex + ".png";
}
imageInit.src = path;
}else{
displayAngle(angleIndex);
if(angleIndex > numAngles-2){
logger.log("turns : " + loopcounter++ +" C: " + layerPathsCounter);
clearCanvases();
loadImage(0,0,0);
layerPathsCounter++;
if(layerPathsCounter > layerPathsArr.length-1){
layerPathsCounter = 0;
}
layerPaths = layerPathsArr[layerPathsCounter];
}else{
loadImage(pathIndex+angleStep,0,angleIndex+1);
}
}
}
heres a couple of helper functions :
function clearCanvases(){
for(var i = 0; i < numAngles; i++){
var canvas = canvasAngles[i];
var canvasContext = canvas.getContext('2d');
canvasContext.clearRect(0,0,drawingCanvas.width, drawingCanvas.height);
}
}
function displayAngle(index){
context.clearRect(0,0,drawingCanvas.width, drawingCanvas.height);
var canvas = canvasAngles[index];
context.drawImage(canvas,0,0);
}
HTML :
<body >
<canvas id="myDrawing" width="900" height="373">
<p>Your browser doesn't support canvas. (HEHE)</p>
</canvas>
</body>
It seems like you can only load ~6.5 mb in one tab. And as far as I know there is no way to free up this memory (?)
this will load about 13 500kb imqages on an ipad and then stop..,
http://www.roblaplaca.com/examples/ipadImageLoading/img.html

Categories

Resources