Javascript loop logic error with Tone.js Tone.Transport.scheduleRepeat - javascript

Just playing around with Tone.js and not quite understanding the finer points of Tone.Transport.scheduleRepeat
When using a function more than once the sounds start to distort and timing changes.
I'm following this tutorial, and trying to duplicate within Codepen.
My "playScore" function changes every time I run it despite the index counter being reset to zero.
<!-- HTML -- >
<button onclick="playScore()">
TEST SCORE
</button>
//js
function playScore() {
console.clear();
console.log("TEST SCORE");
const synthScore = new Tone.Synth
synthScore.oscillator.type = 'sine';
synthScore.toMaster();
//synth.triggerAttackRelease ('E4', '8n' );
const notes = [
'C4', 'E4', 'G4',
'C5', 'E5', 'G5'
];
let index = 0;
Tone.Transport.scheduleRepeat(time => {
repeat(time);
}, "8n");
function repeat(time) {
console.log("index: " + index + " notes.length: " + notes.length);
let note = notes[index % notes.length];
synthScore.triggerAttackRelease(note, '8n', time);
console.log("note:" + note + " time: " + time);
index++;
}
Tone.Transport.start();
setTimeout(() => {
Tone.Transport.stop();
}, 5000)
// Tone.Transport.bpm.value = 120
I expect the same notes to be played the same way in the same order.
Instead, I'm seeing it change on each iteration.
I found that it's because apparently, I have 2 index variable and logic with local instance within the function and global outside that function.

Found a simple solution.
The button function "playScore" simply starts and stops the Tone.Transport
The notes pick up right where they let off rather than starting at the top of the array.
console.clear();
const synthScore = new Tone.Synth
synthScore.oscillator.type = 'sine';
synthScore.toMaster();
//synth.triggerAttackRelease ('E4', '8n' );
const notes = [
'C4', 'E4', 'G4',
'C5', 'E5', 'G5'
];
let speed = '8n'
let index = 0;
Tone.Transport.scheduleRepeat(time => {
repeat(time);
}, "8n");
let repeat = ( time ) => {
console.log("index: " + index + " notes.length: " + notes.length);
let note = notes[index % notes.length];
//console.log(note)
synthScore.triggerAttackRelease(note, '8n', time);
addOutput(note, '8n')
index++;
}
function playScore() {
console.log("TEST SCORE");
Tone.Transport.start();
setTimeout(() => {
Tone.Transport.stop();
}, 5000);
// Tone.Transport.bpm.value = 120
}

Related

setTimeout in object method continues to run after object is destroyed

I have a class called Bullet which is essentially a div on a space invader webpage. When this bullet gets 'fired' I call a method which gradually moves the 'bullet' up the screen.
When the bullet gets to the edge of the screen I want to remove the whole bullet object from memory. However, the setTimeout loop continues to run even after I've deleted it (I think).
I'm sure there is a better way to do this! Perhaps it's foolish to run the loop like this?
TIA
this.bulletmove = new CustomEvent("bulletmove",{detail:this.name});
...
/**
* moves the bullet up the screen gradually
*/
fire(){
var that = this;
setTimeout(function(){
that.moveUp();
window.dispatchEvent(that.bulletmove);
that.fire();
},50);
}
The event is picked up in a controller script which checks if the bullet has reached the edge of the screen at which point it is deleted:
window.addEventListener('bulletmove', function(evt) {
checkCollision(evt);
},false);
...
/**
*Check if the bullet has gone off screen and deletes it
**/
function checkCollision(e){
var bulletName = e.detail;
var bullet = bullets[bulletName];
//check if the bullet has gone off screen
if (bullet.bottom < 0){
bullet.destroy;
delete bullets[e.detail];
bullet=null;
}
}
Have you tried a clearTimeout method to stop the setTimeout from firing?
https://www.freecodecamp.org/news/javascript-settimeout-how-to-set-a-timer-in-javascript-or-sleep-for-n-seconds/
const fireBullet = setTimeout(function(){
that.moveUp();
window.dispatchEvent(that.bulletmove);
that.fire();
},50);
clearTimeout(fireBullet)
I think you should use setInterval instead of calling fire() again - by calling that function, a new setTimeout is created (with a new handler); before the removal of the object, you call obj.halt(), and that clears the setInterval correctly.
const obj = {
name: "objName",
bulletmove() {
return new CustomEvent("bulletmove", {
detail: this.name
})
},
halt() {
clearInterval(this.intervalHandler)
},
intervalHandler: null,
fire() {
const handler = setInterval(() => {
// this.moveUp()
// console.log("move up")
window.dispatchEvent(this.bulletmove())
// this.fire()
}, 500)
this.intervalHandler = handler
},
}
let i = 0
window.addEventListener('bulletmove', function(e) {
// this if-else if mocks the collision detection
// expected: log obj.name 5 times, then clear the interval,
// then event should not be called anymore
if (i < 5) {
console.log(i, e.detail)
} else if (i < 8) {
obj.halt()
console.log(i)
} else if (i < 100) {
console.log(i, e.detail)
}
i++
})
obj.fire()
ANOTHER WAY
A cleaner approach would be if the fire method returned its own "clear function", and you could use that in the event handling:
const obj = {
name: "objName",
bulletmove() {
return new CustomEvent("bulletmove", {
detail: this.name
})
},
fire() {
const handler = setInterval(() => {
// this.moveUp()
// console.log("move up")
window.dispatchEvent(this.bulletmove())
// this.fire()
}, 500)
return () => clearInterval(handler)
},
}
let i = 0
const fireHandler = obj.fire()
const eventHandler = (clearFn) => (e) => {
// this if-else if mocks the collision detection
// expected: log obj.name 5 times, then clear the interval,
// then event should not be called anymore
if (i < 5) {
console.log(i, e.detail)
} else if (i < 8) {
clearFn()
console.log(i)
} else if (i < 100) {
console.log(i, e.detail)
}
i++
}
const eventHandlerWithRemoveFn = eventHandler(fireHandler)
window.addEventListener('bulletmove', eventHandlerWithRemoveFn)
The drawback of this method is that you need to add each object's event handler separately to the window, its benefit is more control, cleaner code (no need to save that handler in the object).
A MODIFIED VERSION FOR MULTIPLE INTERVALS
This is a version of the previous solution, where the clearing functions are stored in the window object:
const eventHandler = (e) => {
const i = e.detail.eventCounter
if (i < 3) {
console.log(i, e.detail.name)
} else if (i < 4) {
window.bulletIntervals[e.detail.name]()
console.log(i, e.detail.name + " is halted")
} else if (i < 100) {
console.log(i, e.detail.name)
}
}
const getBullet = (i) => ({
eventCounter: i, // only for mocking!
name: `objName-${i}`,
bulletmove() {
return new CustomEvent("bulletmove", {
detail: {
name: this.name,
eventCounter: this.eventCounter,
}
})
},
fire() {
const handler = setInterval(() => {
window.dispatchEvent(this.bulletmove())
this.eventCounter++
}, 500)
if (!window.bulletIntervals) window.bulletIntervals = {}
window.bulletIntervals[this.name] = () => clearInterval(handler)
},
})
const bullets = [
getBullet(0),
getBullet(1),
getBullet(2),
]
const fireAll = (bullets) => {
window.addEventListener("bulletmove", eventHandler)
bullets.forEach((bullet) => {
bullet.fire()
})
}
fireAll(bullets)
I would use RxJS to monitor the progress of your bullets.
In the example below I have three different bullets. Each within its own boundary. Once fired, they will immediately stop when they exit their box.
For each bullet we have an "animation frame" observable that emits when such a frame is made available by the browser (internally RxJS uses requestAnimationFrame for this). At that point we check whether the bullet is still within its parent bounding box. If it is we move it otherwise we don't and the subscription to the animation frame stream automatically ends.
const rightPos = el => el.getBoundingClientRect().right;
const moveBullet = (sel, pos) =>
document.querySelector(sel)
.style.left = `${pos}px`;
const fire = (bullet) => {
const el = document.querySelector(bullet);
const parentPos = rightPos(el.parentNode);
return animationFrames().pipe(
map(() => rightPos(el)),
takeWhile(pos => pos < parentPos)
);
}
const bullet1$ = fire('#bullet1');
const bullet2$ = fire('#bullet2');
const bullet3$ = fire('#bullet3');
const fire$ = fromEvent(document.querySelector('button'),'click');
fire$.subscribe(() => {
bullet1$.subscribe(pos => moveBullet('#bullet1', pos+1));
bullet2$.subscribe(pos => moveBullet('#bullet2', pos+1));
bullet3$.subscribe(pos => moveBullet('#bullet3', pos+1));
});
div {
height: 30px;
border: 1px solid black;
margin: 5px;
position: relative;
}
span { position: absolute; }
<script src="https://unpkg.com/rxjs#7.5.7/dist/bundles/rxjs.umd.min.js"></script>
<script>
const {animationFrames, fromEvent} = rxjs;
const {map, takeWhile} = rxjs.operators;
</script>
<div style="width:150px"><span id="bullet1">🏉</span></div>
<div style="width:300px"><span id="bullet2">🥏</span></div>
<div style="width:450px"><span id="bullet3">⚽️</span></div>
<button>Fire!</button>
In the following code, an outer div forms the boundaries of a playfield and all game elements inside the playfield are represented by divs.
Game state consists of an array of game elements (in this instance: one ship and zero or more bullets). Once an element is no longer visible (here: simply off the right-hand side of the playfield), it is removed from game state.
The game loop uses requestAnimationFrame to repeatedly render the game state to the playfield.
Bullet position is calculated using the time of firing and the time elapsed (I added a little randomness to bullet velocity just for fun).
Game elements such as bullets have an associated generator function called as part of the game loop, to retrieve the next state of the element (a bullet "moves by itself" after the initial appearance).
Firing a bullet in this design is as simple as creating a new bullet object with an initial position and an instance of a generator function to account for its trajectory; and then adding that pair to the game state.
const elem = ({ kind = 'div', classN = '' }) => {
const el = document.createElement(kind)
el.classList.add(classN)
return el
}
const applyStyle = (el, style) =>
(Object.entries(style)
.forEach(([k, v]) => el.style[k] = v), el)
const cssPixels = (str) => +(str.slice(0, -2))
const isVisible = (left) =>
cssPixels(left) < cssPixels(playfield.style.width)
const createPlayfield = () =>
applyStyle(elem({ classN: 'playfield' }), { width: '300px' })
const createShip = (startLeft, width) =>
[{ classN: 'ship', style: { left: startLeft, width } }, null]
const createBullet = (startLeft) => {
const b = {
classN: 'bullet',
style: { left: startLeft },
firingTime: +new Date(),
velocity: 0.5,
velocitySeed: Number('1.' + ~~(Math.random() * 9)),
startLeft
}
const g = bulletStateGen(b)
return [ b, () => g.next() ]
}
const bulletPos = ({ firingTime,
startLeft,
velocity,
velocitySeed }, now = +new Date()) =>
`${~~(velocity * (now - firingTime) * velocitySeed + cssPixels(startLeft))}px`
const bulletStateGen = function*(b) {
while (1) {
const left = bulletPos(b)
if (!isVisible(left))
break
b.style = { left }
yield(b)
}
}
const fire = (startLeft) =>
state.unshift(createBullet(startLeft))
const tick = () =>
state = state.reduce((acc, [o, next]) => {
if (!next)
return acc.push([o, next]), acc
const { value, done } = next()
if (done)
return acc
return acc.push([value, next]), acc
}, [])
const blank = () => playfield.innerHTML = ''
const render = () => {
blank()
state.forEach(([{ classN, style = {} }]) =>
playfield.appendChild(applyStyle(elem({ classN }), style)))
}
let ship = createShip('10px', '50px')
let state = [ship]
let playfield = createPlayfield()
const gameLoop = () =>
(render(), tick(), requestAnimationFrame(gameLoop))
const init = () => {
document.body.appendChild(playfield)
document.body.onkeyup = (e) =>
e.key === " "
&& fire(`${cssPixels(ship[0].style.left) + cssPixels(ship[0].style.width)}px`)
}
init()
gameLoop(state, playfield)
.playfield {
height: 300px;
background-color: black;
position: relative;
}
.ship {
top: 138px;
height: 50px;
background-color: gold;
position: absolute;
border-radius: 7px 22px 22px 7px;
}
.bullet {
top: 163px;
width: 10px;
height: 2px;
background-color: silver;
position: absolute;
}
Click on the game to focus it, and then press spacebar to fire!

How to add number to a start value number every few seconds using JS and HTML5

Im building a browser real time text based game. Im new to js and all that right. I have a feature where the player requires resources to lets say upgrade a building or build a certain unit.
Lets say for my question, I have a metal mine with 100 metal in it and I want to have it add 10 metal ever 3 seconds to the already existing 100 metal, and then stops adding 10 metal when it reaches a set cap of lets say 500 metal.
I know I have to use JS to get that right but I dont know how to code it and implement it onto the client side.
I dont have any JS examples but basically have this html code.
<h3><span class="color-1">Metal: 100</span> | <span class="color-2">Gas: 100</span> | <span class="color-3">Oil: 100</span> | <span class="color-4">Power: 470</span></h3>
Okay and then I want to add a function that when the player builds something that costs lets say 50 metal, I want that function to subtract 50 from its total already and then start adding 10 metal every 3 seconds.
An alternative to Kobe's approach may be to create some fire&forget objects.
Consider the class MetalMine
class MetalMine {
constructor () {
this.amount = 100
this.timer = setInterval(this.frame.bind(this), this.framePeriod)
}
frame () {
this.amount = Math.min(this.amount + this.frameQuantity, this.maxQuantity)
if (this.amount === this.maxQuantity) {
clearInterval(this.timer)
}
}
framePeriod = 3000
frameQuantity = 10
maxQuantity = 500
}
you could instantiate some mines and forget about them.
let mine1 = new MetalMine()
let mine2 = new MetalMine()
let mine3 = new MetalMine()
Possibly you may give each of them a dom node, and mine1, ..., mine3 would fill the dom node's html itself.
class MetalMine{
constructor (el) {
this.$el = el
...
}
frame () {
this.amount = ...
this.$el.textContent = this.amount
...
}
...
}
let mine1 = new MetalMine(document.querySelector('#somemine'))
let mine2 = new MetalMine(document.querySelector('#somemine1'))
let mine3 = new MetalMine(document.querySelector('#somemine2'))
Or you could go a bit more funky and extend some dom node:
class MetalMine extends HTMLElement{
constructor () {
super()
this.attachShadow({ mode: 'open' })
this.span = document.createElement('span')
this.amount = 100
this.timer = setInterval(this.frame.bind(this), this.framePeriod)
this.frame()
this.shadowRoot.append(this.span)
}
frame () {
// check yourself that frame is not called anymore once clearInterval
// has been called
console.log('called')
this.amount = Math.min(this.amount + this.frameQuantity, this.maxQuantity)
this.span.textContent = `metal ${this.amount}`
if (this.amount === this.maxQuantity) {
clearInterval(this.timer)
}
}
//set the constants to the prototype
framePeriod = 3000
frameQuantity = 10
maxQuantity = 130
}
customElements.define('my-metalmine', MetalMine)
//create some mine with delay
let body = document.querySelector('body')
body.append(document.createElement('my-metalmine'))
setTimeout( _ => {
body.append(document.createElement('my-metalmine'))
setTimeout(_ => {
body.append(document.createElement('my-metalmine'))
}, 1000)
}, 1000)
<body></body>
Here's a small example of your mine using a setInterval:
const mEl = document.getElementById('metal')
const user = {
resources: {
metal: 100
},
caps: {
// ...
metal: 500
}
}
const modifyMetal = m => {
if (!typeof newMetal === 'number') {
throw new Error('Invalid paramater type passed to modifyMetal')
}
if (user.resources.metal + m < 0 || user.resources.metal === user.caps.metal) {
return null
}
if (user.resources.metal + m >= user.caps.metal) {
return mEl.textContent = user.resources.metal = user.caps.metal
}
return mEl.textContent = user.resources.metal += m
}
const mine = setInterval(() => {
modifyMetal(3)
}, 300)
const build = () => {
modifyMetal(-50) ? console.log('Build Action') : console.log('Not enough metal')
}
<span id="metal">100</span> metal
<br>
<button onclick="build()">Build</button>

extracting an anonymous function

CodePen
On line 19, the event listener calls an anonymous function, I'd like to break this out into its own function, but I also think I need to be passing a 3rd argument in. possibly the padz class, or maybe the audio tags.. maybe a combination of both, but not sure of the syntax to do that.
JavaScript
var clickCounter = 0;
var currentLevel = 0;
var simon_sSequence = [];
var player_sInput = [];
const audioTapper = document.querySelector("#audioT");
const audioError = document.querySelector("#audioE");
const levelDisplay = document.getElementById("level_Display");
const simonzSequence = document.getElementById("simon_sSequence");
const playerInput = document.getElementById("player_sInput");
const start_Tapper = document.querySelector(".startTapper");
const pads = document.querySelectorAll(".padz");
// Convert the padz list to an array that we can iterate over using .forEach()
Array.from(pads).forEach((pad, index) => {
// Get the associated audio element nested in the padz-div
const audio = pad.querySelector("audio");
// Add a click listener to each pad which
// will play the audio, push the index to
// the user input array, and update the span
pad.addEventListener("click", () => {
player_sInput.push(index);
if (player_sInput[clickCounter] !== simon_sSequence[clickCounter]) {
audioError.play();
$("body").animate({ backgroundColor: "red" }, 100);
$("#container").effect( "shake", {times:100}, 1000, 'linear' );
$("body").animate({ backgroundColor: "gray" }, 5000);
//console.log("wrong");
} else { //end of if
audio.play();
playerInput.textContent = "Player's reply " + player_sInput;
clickCounter++;
//console.log(clickCounter);
} //end of else
}); //end of EventListener
}); //end of Array.from(pads).forEach((pad, index)
start_Tapper.addEventListener("click", (resetStart)); //end of EventListener
function resetStart() {
//generate a random number & push it to simon_sSequence
simon_sSequence.push(Math.floor(Math.random() * 4));
simonzSequence.textContent = "Simon says " + simon_sSequence;
playerInput.textContent = "Player's reply " + player_sInput;
//for each in the array set time interval(300ms);
//dipslay hover effect
//play pad sound
audioTapper.play();
player_sInput = []; //clear player's input for this turn
clickCounter = 0;
currentLevel++;
levelDisplay.textContent = "Level: " + currentLevel + " " + simon_sSequence.length + " " + player_sInput.length;
}

Javascript Performance Testing, identical tests at different speeds

I've got some code for testing performance of jQuery selectors. I run the tests 3 times with 100000 repetitions in a browser console. The first test is always the slowest, then, the following two are nearly equal in speed. Has anybody an idea why?
Here's the fiddle: http://jsfiddle.net/05xq70na/, the execution times are logged in browser console.
var repetitions = 100000;
var html = '<ul><li id="parent"><div id="child" class="child" value="test"></div></li></ul>';
var time = [];
var finished = false;
var tests = {
"SelectorById": function(){
var element;
element = $("#child");
},
"SelectorByClass": function(){
var element;
element = $(".child");
},
"SelectorByTag": function(){
var element;
element = $("li");
}
}
function showResults(loopCounter) {
if(!finished) {
finished = true;
console.log("\nResults (" + loopCounter + " repetitions):");
for(executionTime in time) {
console.log(executionTime + ": " + (time[executionTime] / loopCounter) + " ms");
}
}
}
function perform(test, string) {
var startTime,
endTime,
nextTest = tests[test];
if(time[test + string] === undefined) {
time[test + string] = 0;
}
startTime = performance.now();
nextTest();
endTime = performance.now();
time[test + string] += (endTime - startTime);
}
function benchmark(tests) {
for(var i=0, loopCounter=1; i<repetitions; i++) {
// console.log('repetition ' + loopCounter);
for(test in tests) {
// console.log('\texecute ' + test);
perform(test, "1");
perform(test, "2");
perform(test, "3");
}
loopCounter++;
if(loopCounter>repetitions) {
showResults(--loopCounter);
};
}
}
document.getElementsByTagName('body')[0].innerHTML = html;
benchmark(tests);
I have tested the simple jQuery selector in JSLitmus (JSFiddle).
JSLitmus.test('jQuery ID selector 1', function(counter) {
while(counter--) {
$('#test');
}
});
...
The result is like:
The performance of ID selectors vary every time, but there is no fixed pattern.
I update your code to run every tests for 7 times: JSFiddle. Then the first run also spends most time, however the rest spend some time comparable. Since JSLitmus performs something called calibration, I am afraid your testing code is not so fare, especially when there are so many function calls.

Javascript for loop doesn't work (adding numbers to a total)

I am using Jasmine for JS testing, and unfortunately I can't get the following test to pass.
it('should know the total game score', function() {
frame1 = new Frame;
frame2 = new Frame;
game = new Game;
frame1.score(3, 4);
frame2.score(5, 5);
expect(game.totalScore()).toEqual(17)
});
The error message I get is as follows: Error: Expected 0 to equal 17.
The code is as follows:
function Game() {
this.scorecard = []
};
Game.prototype.add = function(frame) {
this.scorecard.push(frame)
};
// Why is this not working!!???
Game.prototype.totalScore = function() {
total = 0;
for(i = 0; i < this.scorecard.length; i++)
{
total +=this.scorecard[i].rollOne + this.scorecard[i].rollTwo;
}
return total;
};
function Frame() {};
Frame.prototype.score = function(first_roll, second_roll) {
this.rollOne = first_roll;
this.rollTwo = second_roll;
return this
};
Frame.prototype.isStrike = function() {
return (this.rollOne === 10);
};
Frame.prototype.isSpare = function() {
return (this.rollOne + this.rollTwo === 10) && (this.rollOne !== 10)
};
Adding the numbers together manually seems to work e.g. total = game.scorecard[0].rollOne + this.scorecard[0].rollTwo , but the for loop (even though it looks correct) doesn't seem to work. Any help would be greatly appreciated :)
I am not pretty sure, but it seems that you are not calling the "Add" method, so no data is added to the scorecard.
You have to add the Frames to your game i guess
it('should know the total game score', function () {
frame1 = new Frame;
frame2 = new Frame;
game = new Game;
// those lines are missing
game.add(frame1);
game.add(frame2);
frame1.score(3, 4);
frame2.score(5, 5);
expect(17).toEqual(game.totalScore())
});
otherwise, the scorecard-array is empty and the total score is therefore equal to 0.
missing (so no data is added to the scorecard.)
game.Add(frame1);
game.Add(frame2);

Categories

Resources