Within my code (the code below is a very shortened version), I am creating oscillators (https://p5js.org/reference/#/p5.Oscillator) in draw. Eventually the audio starts crackling and being all weird, and I assume this is because the oscillators aren't being garbage collected? When looking at the Memory tab in Inspect Element, it continously goes up, and the audio goes crackly eventually.
I tried to dereference the oscillators using the two functions in my disconnect variable/function. This sets the oscillator to null after a specific amount of time I hope. I wasn't sure how to set a reference argument to null.
Maybe the p5 sound library still keeps references for them?
function createOsc(grid) {
let osc = new p5.Oscillator();
if (grids[grid].type == 1) {
osc.setType("triangle");
} else if (grids[grid].type == 2) {
osc.setType("sawtooth");
} else if (grids[grid].type == 3) {
osc.setType("square");
}
return osc;
}
function createEnv(grid) {
let env = new p5.Envelope();
env.set(grids[grid].ADSR.attack, grids[grid].vol, grids[grid].ADSR.decay, grids[grid].ADSR.sustain * grids[grid].vol, grids[grid].ADSR.release, 0);
return env;
}
let disconnect = {
list: [],
osc: function(osc, time) {
this.list.push([osc, time, 0]);
},
update: function() {
for (i = 0; i < this.list.length; i++) {
this.list[i][2] += 1 / 60.0;
if (this.list[i][2] >= this.list[i][1]) {
this.list[i][0].a.disconnect();
this.list[i][0].a = null;
this.list.splice(i, 1);
i += -1;
}
}
}
}
function draw() {
let osc = createOsc(0);
let env = createEnv(0);
osc.start();
osc.freq(420);
env.triggerAttack(osc);
env.triggerRelease(osc, env.aTime + env.dTime);
osc.stop(env.aTime + env.dTime + env.rTime);
disconnect.osc({a: osc}, env.aTime + env.dTime + env.rTime);
}
I attempted to use the PolySynth (https://p5js.org/reference/#/p5.PolySynth) object, but I couldn't figure out how to change the waveforms or anything due to lack of clear documentation.
Edit:
This new standalone code produces the same crackling audio after a while. I am also now using Millis() as suggested. Same issue with memory constantly increasing.
let disconnect = {
list: [],
osc: function(osc, time) {
this.list.push([osc, time, millis()]);
},
update: function() {
for (i = 0; i < this.list.length; i++) {
if (millis() - this.list[i][2] >= this.list[i][1]) {
this.list[i][0].a.disconnect();
this.list[i][0].a = null;
this.list.splice(i, 1);
i += -1;
}
}
}
}
function createOsc() {
let osc = new p5.Oscillator();
osc.setType("triangle");
return osc;
}
function createEnv() {
let env = new p5.Envelope();
env.set(0.1, 1, 0.1, 0.8, 0.1, 0);
return env;
}
function setup() {
frameRate(60);
}
function draw() {
disconnect.update();
print(disconnect.list.length);
if (frameCount % 2 == 0) {
let osc = createOsc();
let env = createEnv();
osc.start();
osc.freq(420);
env.triggerAttack(osc);
env.triggerRelease(osc, 0.2);
osc.stop(0.3);
disconnect.osc({a: osc}, 300);
}
}
I have this simulation JavaScript coffee machine to make and I'm having problem with two methods, one of which adds water (coffee eg.) and other that empty's it.
This is what I got for first method:
let coffeeMachine = {
water: 400,
coffee: 10,
milk: 100,
credit: 100,
waterStatus: function () {
document.getElementById('water-status').innerText = this.water;
},
addWater: function () {
let addWater = prompt('Unesite kolicinu vode koju zelite da dodate');
if (addWater === null || addWater.trim().length === 0) {
alert('Morate uneti koliko vode zelite');
return;
} else if (isNaN(addWater) || addWater.startsWith('-') || addWater % 1 !== 0) {
alert('Unos mora biti pozitivna brojcana vrednost');
return;
} else if (this.water < 400) {
addWater = Number(addWater);
this.water += addWater;
} else {
alert('Maksimalan unos vode je 400');
return;
}
this.waterStatus();
},
Problem with this one is that the total value can not be greater that 400, and it only works when I first enter the value that is greater than 400 (alert pops up), but if add 100, and than 500 it will write it (600), not considering the condition.
Is it that I'm not creating new variable that will collect the sum of this.water and addWather, or I'm missing something else?
Second method needs to empty water and check if the current status of the water is bigger than the value we forwarder to prompt.
I have this:
emptyWater: function (water) {
if (this.water > addWater) {
this.water -= addWater;
this.waterStatus(water);
document.getElementById('message').innerText = 'Pouring water';
} else {
document.getElementById('message').innerText = 'Machine is out of water \n';
}
},
In the object itself I have predefined values for water, coffee eg.
Those values I collected in water status function, but did I not made it dynamic?
waterStatus: function () {
document.getElementById('water-status').innerText = this.water;
},
Check for the new total value, so the condition will work:
....
} else if (this.water + Number(addWater) < 400) {
addWater = Number(addWater);
this.water += addWater;
...
Update
I think you should change the parameter water to addWater in the emptyWater function:
emptyWater: function (addWater) {
if (this.water > addWater) {
this.water -= addWater;
this.waterStatus();
...
You can use it in the addWater function, as:
...
} else if (this.water < 400) {
addWater = Number(addWater);
this.water += addWater;
this.emptyWater(addWater);
...
And the waterStatus would be:
waterStatus: function () {
document.getElementById('water-status').innerText = this.water;
},
I have a problem with a countdown function I've created for an idiotic fight game I'm developing as an assignment.
I have the following method:
/* Countdown timer before fight */
startTimer() {
this.countDownPanel = true;
if (this.countDownNumbers > 0) {
this.countDown = setInterval(() => {
this.countDownNumbers--;
}, 1000);
this.textDownsizer = setInterval(() => {
this.countDownTextSize--;
}, 10);
}
},
And the following watcher that stops the countdown:
/* Countdown Watcher */
countDownNumbers() {
this.countDownTextSize = 100;
if (this.countDownNumbers < 0) {
clearInterval(this.textDownsizer);
this.textDownsizer = null;
clearInterval(this.countDown);
this.countDown = null;
this.countDownPanel = false;
}
}
},
The problem is that even after I use the clearInterval() through the Vue DevTools I can see the timer still running until countDownNumbers reaches 0. But the case is that it happens a while after the actual numbers on the screen are down to 0. And when I reset the game, running the same instance, the countdown goes nuts and causes a memory leak. Is there a better way of doing it?
Here's the complete Js code:
new Vue({
el: '#app',
data: {
gameStatus: 'disclaimer', //Status of the app, it can be (so far) 'disclaimer', 'startScreen', 'playStage', 'looseScreen', 'wonScreen', 'menuScreen'
backgroundAudio: new Array(), //Array of current playing background sounds
blurFilter: true, //Blur shown at the start of the app
audioTracks: ["sound/battle.mp3", "sound/dungeon.wav", "sound/echo.ogg"], //List of file in the folder 'sound'
playList: new Array(), //Array with a collection of Audio objects that are playable
/*
The health points function in an inverse way. The subject dies when it reaches 100
The health bar div width % is calculated subtracting the current health of the avatar of 100.
*/
healthHuman: 0,
healthComputer: 0,
muted: false, //Obviously determines if the audio is activated or not
humanStatus: true, //Defines if the human player is dead or alive
computerStatus: true, //Defines if the computer player is dead or alive
/* Collection of string that are outputed when a avatar dies */
humanDiesPhrases: [
"You've just been killed and that's why you're seeing your own life flash in front of you.",
"It's almost as if your soul has taken over your body in a frantic attempt to preserve the memory of what you love.",
"The problem is that when your soul leaves your body, it's physically incapable of going back in and if it doesn't try to go back in and make the same decision, it could start experiencing the same things all over again.",
"That was sad and gruesome and an almost poetic way. Anyway, you're dead. The rats are anxious to eat your remains.",
"Looks like the monster will be eating something besides rats today",
"Woow! That must have hurted! Dude! Are you okay? Dude? Dude... Ohh, you're dead. Sorry about that",
"Your death looks like an abstract painting. To me, not for you. For you that must have hurted like hell.",
"Yep! Looks like you're dead. Can I have your sowrd?",
"You died! Was killed to be more exact. Now your opponent drags you to one of many dark pits inside the dungeon.",
"Oh my god, how can you suck in such idiotic game? I know that the game is lame, but you just raised the bar fella.",
"You're not very good at this are you? Have you ever considered trying gardening?"
],
computerDiesPhrases: [
"You've just killed it! Or should I say him? Or her? You didn't ask, did you? How considerate of you.",
"GG mah boy, keep it like this and I'm sure you're going places. Maybe...",
"Wow that was 2 minutes less of your life, how does it feel? You're not having those back... Are you still reading?",
"Oh my, my... Why such violence? Have you considered petting it? Maybe it was just saying hello. Weirdo.",
"Well, it's dead. Now what? Are you going to eat it? Of course not silly, this is just a game. Back to being productive",
"AM I being nosy or you should be doind something else instead? I mean, killing virtual monsters?",
"Good kill dude! But, have you considered that it maybe was the last of its kind? That's disturbing.",
"You've just killed the creature! Good for you girl!",
"Wait! Have you considered trying to... Forget it, it's dead already",
"Oh man! (or woman!). That was gruesome. Wait a minute, have you just farted? Ewww! Ohh sorry, that one was mine.",
"Why did you do that? Such violence. Well you killed it, just like that. That's aliens haven't made contact with us yet...Savage!"
],
humanParts: [
"groin", "left eye", "upper lip", "right ear", "left leg", "tiny finger", "balls", "right knee", "face", "head", "lung", "chest", "chin", "middle finger", "you know where", "neck", "nose", "foot", "ribs", "teeth",
],
monsterParts: [
"center most tentacle", "foremost tooth", "tiny tentacle", "lady parts", "boy parts", "gum", "ass", "iris", "eyeball", "feelings", "wise tooth"
],
fightingMovements: [
"bite", "slapped", "punched", "kicked", "headbutted", "scratched", "hit", "punctured", "fingered", "poundded", "bashed"
],
countDownNumbers: 5, //Countdown counter =)
countDown: '', //Stores the main countdown
countDownTextSize: 100, //Obvious
textDownsizer: '', //Stores the timer responsible to downsize the font of the countdown
logger: new Array(), //Array responsible for storing all messages that will be shown to the player
countDownPanel: false, //Determines if the countdown is to be shown or not
},
computed: {
/* Unset the blur when the disclaimer modal goes away */
flipBlur() {
if (this.gameStatus != 'disclaimer') {
return this.blurFilter = false;
} else {
return true;
}
},
},
watch: {
//Wacth the gameStatus and make changes accordingly
gameStatus() {
//Background Audio Controler
//Tracks
echoSound = this.playList['echo'];
battleSound = this.playList['battle'];
dungeonSound = this.playList['dungeon'];
//Game status DJ
if (this.gameStatus == 'startScreen') {
this.loopPlay(echoSound);
this.loopPlay(dungeonSound);
} else if (this.gameStatus == 'playStage') {
//Add battle song to the background
this.loopPlay(battleSound);
battleSound.volume = 0.25;
//Trigger the coundown
this.startTimer();
}
},
muted() {
if (this.muted == true) {
this.backgroundAudio.forEach(element => {
element.pause();
});
} else {
this.backgroundAudio.forEach(element => {
element.play();
});
}
},
/* Health checkers // Killer */
healthHuman() {
if (this.healthHuman >= 100) {
this.healthHuman = 100;
this.humanStatus = false;
this.humanDied();
}
},
healthComputer() {
if (this.healthComputer >= 100) {
this.healthComputer = 100;
this.computerStatus = false;
this.computerDied();
}
},
/* Logger Wachter - Maintain only the last 3 elements of the logger */
logger() {
if (this.logger.length > 3) {
this.logger.pop();
}
},
/* Countdown Watcher */
countDownNumbers() {
this.countDownTextSize = 100;
if (this.countDownNumbers < 0) {
clearInterval(this.textDownsizer);
this.textDownsizer = null;
clearInterval(this.countDown);
this.countDown = null;
this.countDownPanel = false;
}
}
},
beforeDestroy() {
clearInterval(this.textDownsizer);
},
created: function () {
this.buildPlayList();
},
methods: {
/* Background soundtracks and sound effects */
loopPlay(audioTrack) {
if (audioTrack) {
audioTrack.play();
audioTrack.loop = true;
this.backgroundAudio.push(audioTrack);
}
},
buildPlayList() {
this.audioTracks.forEach(element => {
trackName = element.substring(element.indexOf('/') + 1);
trackName = trackName.substring(0, trackName.indexOf('.'));
this.playList[trackName] = new Audio(element);
});
},
/*
HP monitoring and controller
These two functions return the css property 'width' of the health bars.
*/
heartMonitorHuman() {
return {
width: this.healthHuman + '%'
}
},
heartMonitorComputer() {
return {
width: this.healthComputer + '%'
}
},
/* Generates random hit points */
hitGenerator() {
var hit = Math.round(Math.random() * 20);
return hit;
},
/* Controlls the timer before the fight */
counterFontSize() {
return {
'fontSize': this.countDownTextSize + 'vw'
}
},
/* Hits the human */
hitHuman() {
if (this.healthHuman < 100) {
var willHitPoints = Math.floor(this.hitGenerator() * 1.1); //Gives the monster 10% more attack power on average
this.healthHuman += willHitPoints;
this.logHandler('You got ' + this.randomFightingMoves() + ' on the ' + this.humanHitDesc() + ' and lost ' + willHitPoints + ' HP');
} else {
this.humanDied();
}
},
/* Hits the computer */
hitComputer() {
if (this.healthComputer < 100 && this.countDownNumbers <= 0) {
var willHitPoints = this.hitGenerator();
this.healthComputer += willHitPoints;
this.logHandler('You ' + this.randomFightingMoves() + ' the monster\'s ' + this.monsterHitDesc() + ' and it lost ' + willHitPoints + ' HP');
//Hits the human player back
this.hitHuman();
} else {
this.computerDied();
}
},
/* Shows phrases regarding the avatar's deaths */
humanDied() {
if (this.humanStatus == false) {
var deathMessage = Math.round(Math.random() * 10);
this.logHandler(this.humanDiesPhrases[deathMessage]);
this.logHandler("You died!");
setTimeout(() => {
this.resetGame();
}, 3000);
}
},
computerDied() {
if (this.computerStatus == false) {
var deathMessage = Math.round(Math.random() * 10);
this.logHandler(this.computerDiesPhrases[deathMessage]);
this.logHandler("You win?");
setTimeout(() => {
this.resetGame();
}, 3000);
}
},
/* Generates a random key to be used with a dictionary of log messages */
keyGen() {
return '_' + Math.random().toString(36).substr(2, 9);
},
/* Creates a dictionary with all log messages generated by the app */
logHandler(msg) {
var newLog = {
id: this.keyGen(),
message: msg,
};
this.logger.unshift(newLog);
},
/* Random hits generators */
humanHitDesc() {
randomNumber = Math.floor(Math.random() * 20);
return this.humanParts[randomNumber];
},
monsterHitDesc() {
randomNumber = Math.floor(Math.random() * 10);
return this.monsterParts[randomNumber];
},
randomFightingMoves() {
randomNumber = Math.floor(Math.random() * 10);
return this.fightingMovements[randomNumber];
},
resetGame() {
this.logger = [];
this.healthHuman = 0;
this.humanStatus = true;
this.healthComputer = 0;
this.computerStatus = true;
this.countDownNumbers = 5;
this.startTimer();
},
/* Countdown timer before fight */
startTimer() {
this.countDownPanel = true;
if (this.countDownNumbers > 0) {
this.countDown = setInterval(() => {
this.countDownNumbers--;
}, 1000);
this.textDownsizer = setInterval(() => {
this.countDownTextSize--;
}, 10);
}
},
}
});
The GitHub repo is: https://github.com/Saiuke/MonsterSlayer
Here's a screen capture of the game:
edited: sorry for the mistake. its fixed now.
Have you tried changing if (this.countDownNumbers < 0) to if (this.countDownNumbers === 0)? That might work. If that does not work then why not just make a simple countdown like this:
let num = 10;
let timer = setInterval(function(){
num = num - 1;
console.log(num);
if(num <= 0){
clearInterval(timer);
}
},1000);
The main issue is that startTimer() does not stop any existing timers before starting new ones. And since startTimer() is indirectly invoked by the user (i.e., humanDied() -> resetGame() -> startTimer(), computerDied() -> resetGame() -> startTimer(), or gameStatus change -> startTimer()), multiple timers can be started unpredictably.
The solution is to stop any existing timers before starting new ones, and to move the expiration logic from the countdownTimer watcher into startTimer():
export default {
watch: {
countDownNumbers() {
this.countDownTextSize = 100;
// XXX: DON'T DO THIS HERE
// if (this.countDownNumbers < 0) {
// clearInterval(this.textDownsizer);
// this.textDownsizer = null;
// clearInterval(this.countDown);
// this.countDown = null;
// this.countDownPanel = false;
// }
}
},
methods: {
startTimer() {
clearInterval(this.countDown);
this.countDown = null;
clearInterval(this.textDownsizer);
this.textDownsizer = null;
if (this.countDownNumbers > 0) {
this.countDown = setInterval(() => {
this.countDownNumbers--;
if (countDownNumbers <= 0) {
this.countDownPanel = false;
}
}, 1000);
this.textDownsizer = setInterval(() => {
this.countDownTextSize--;
}, 10);
}
}
}
}
you can do by this way it is so easy. this code is compatible with Vue-Js 3.
...... youe code......
,mounted() {
this.intervalId = setInterval(this.callStatusFn, 200)
console.log("intervalId "+ this.intervalId)
},
unmounted() {
clearInterval(this.intervalId)
}
I created a scrolling gallery that works as intended, but when you repeatedly press the arrow quickly, it breaks down.
It will recover when you stop, but I would like it more fluid when it is pressed quickly.
const options = {
slideOptionsRight: {
"duration": 1000, //Change how quickly the picture fades in milliseconds.
"direction": "right"
},
slideOptionsLeft: {
"duration": 1000,
"direction": "left"
},
images: [ //Add image names to be added to the scroll here, wrapped in quotation marks.
'#image1',
'#image2',
"#image3",
"#image6"
],
arrowImage: [ //Add arrow images here.
"#image4", //Left arrow.
"#image5" //Right arrow.
],
"interval": 3, //Change time between pictures in seconds.
"imageArray": 0,
"timer": 0,
"leftClick": false,
"clicked": false
}
//Does not need to be touched or changed
$w.onReady(function () {
for (let i = 1; i < options.images.length; i++) {
$w(options.images[i]).hide()
}
const changePicture = () => {
options.timer += 1;
if (options.timer > options.interval || options.clicked) {
if (options.leftClick) {
if (options.imageArray === 0) {
options.images.forEach((e) => {
$w(e).hide("slide", options.slideOptionsRight)
})
// $w(options.images[0]).hide("slide", options.slideOptionsRight);
$w(options.images[options.images.length - 1]).show("slide", options.slideOptionsLeft);
options.imageArray = options.images.length - 1;
} else {
options.images.forEach((e) => {
$w(e).hide("slide", options.slideOptionsRight)
})
// $w(options.images[options.imageArray]).hide("slide", options.slideOptionsRight);
$w(options.images[options.imageArray - 1]).show("slide", options.slideOptionsLeft);
options.imageArray -= 1;
}
options.leftClick = false;
} else {
if (options.imageArray === options.images.length - 1) {
options.images.forEach((e) => {
$w(e).hide("slide", options.slideOptionsLeft)
})
// $w(options.images[options.imageArray]).hide("slide", options.slideOptionsLeft);
$w(options.images[0]).show("slide", options.slideOptionsRight);
options.imageArray = 0;
} else {
options.images.forEach((e) => {
$w(e).hide("slide", options.slideOptionsLeft)
})
// $w(options.images[options.imageArray]).hide("slide", options.slideOptionsLeft);
$w(options.images[options.imageArray + 1]).show("slide", options.slideOptionsRight);
options.imageArray += 1;
}
}
options.timer = 0
} else {
return null;
}
}
let startInterval = setInterval(changePicture, 1000)
$w(options.arrowImage[1]).onClick(() => {
options.clicked = true;
changePicture();
options.clicked = false;
})
$w(options.arrowImage[0]).onClick(() => {
options.leftClick = true;
options.clicked = true;
changePicture();
options.leftClick = false;
options.clicked = false;
})
});
Is there a way to force the slide effect to complete early, or would a complete different implementation be better?
link to a test https://joshsetterstrom.wixsite.com/mysite
I am making a game with rounds that last either 100 minutes OR the time required for totalItemAmount to reach 500.
If "addPlayer" gets triggered when the game has ended, I want to push that player to next round.
I want to be able to destroy it/create new ones flawlessly.
First game always runs well.
Following games go wrong. To sum it up, I feel like the first game ran isn't being properly destroyed after it ends. Obviously, my code is much much larger than what you see down here, but let me ask you, is there any problem with my structure?
function Game(duration, playersForThisRound) {
console.log('NEW GAME')
this.playersForNextRound = [];
this.id = Math.random();
this.finished = 0;
this.players = {};
this.duration = 100;
this.totalItemAmount = 0;
this.endTimeout = setTimeout(() => {
this.end(this);
return
}, this.duration * 60 * 1000);
if (playersForThisRound && playersForThisRound.length > 0) {
Object.keys(this.playersForNextRound).forEach(function(key) {
this.addPlayer(this, key.player, key.items)
});
}
return
}
Game.prototype.addPlayer = function(game, player) {
if (this.finished || this.totalItemAmount >= 500) {
var playerIsInNextRound = 0;
Object.keys(this.playersForNextRound).forEach(function(key) {
if (key.id == player.id) {
playerIsInNextRound = 1
key.items.push(items)
}
})
if (!playerIsInNextRound)
this.playersForNextRound.push({
"id": player.id,
"player": player,
"items": items
});
if (game.totalItemAmount >= 500) {
clearTimeout(game.endTimeout)
this.end(this);
}
return
}
if (!game.players[player.id]) {
game.players[player.id] = new Player(game, player, items);
game.players[player.id].addItems(game, items, function(added) {
game.jackpot += added;
Object.keys(game.players).forEach(function(player) {
game.players[player].chance = game.players[player].getChance(game.jackpot)
});
})
}
Game.prototype.end = function(game) {
game.finished = 1;
clearTimeout(game.endTimeout)
setTimeout(() => {
game = new Game(10, this.playersForNextRound);
}, 1000 * 60 * 10);
}
//To start first game
var game = new Game();
module.exports = game;
Now in another file:
let game = require("./lib/games/roundbased.js");
let test = setInterval(() => {
for (var i = 0; i < 100; i++) {
game.addPlayer(game, {
id: '12345' + i,
nick: "player" + i
},