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);
}
}
Related
let play_button = document.querySelector('#play_button');
let reverb_button = document.querySelector('#reverb_button');
let save_button = document.querySelector("#save_button");
let song, reverb, speed_slider, recorder;
let reverb_on = false;
function preload() {
song = loadSound('music/Mood.mp3');
}
function setup() {
speed_slider = createSlider(0.6, 1.5, 1, 0.1); // min, max, start, step
reverb = new p5.Reverb();
reverb.process(song, 10, 10); // 10 seconds duration, 10% decay
}
function draw() {
let val = 0;
if (reverb_on) val = 1;
reverb.drywet(val); // 1 is all reverb
song.rate(speed_slider.value());
}
function togglePlay() {
if (!song.isPlaying()) {
song.play();
play_button.innerText = "Stop"
} else {
song.pause();
play_button.innerText = "Play"
}
}
function toggleReverb() {
if (!reverb_on) {
reverb_button.innerText = 'off';
reverb_on = true;
} else {
reverb_button.innerText = 'on';
reverb_on = false
}
}
function saveSong() {
saveSound(song, 'mood')
}
Above is my code, trying to give the user an option to save the soundfile after they added different effects to it, such as reverb and speed control.
No matter what method I tried that was on the p5 documentation, including:
saveSound() -> https://p5js.org/reference/#/p5/saveSound
save() -> https://p5js.org/reference/#/p5.SoundFile/save
p5.SoundRecorder passing in the sound file itself.
None of them work and only save the original sound; doesn't save any of the 'changes' you make.
I'm on Firefox 84.0.1, Windows 10, x86_64. I have a very basic WebAudioWorklet synthesiser that maps keys to the frequencies of musical notes. It is generating very choppy audio when a key is held down. This makes me think that there is not enough audio samples being queued for the speaker to play, hence the audio dropping in and out. However, in audio processing terms, I'm performing a very low-intensive task. As a result, I feel like the default Worklet setup should be able to handle this. Here is my code:
syn.js
(async() => {
let a2_hertz = 110.0;
let twelfth_root_of_two = Math.pow(2.0, 1.0 / 12.0);
let audio_cxt = new AudioContext();
await audio_cxt.audioWorklet.addModule("syn-worklet.js", {credentials: "omit"});
let audio_worklet_options = {
numberOfInputs: 0,
numberOfOutputs: 1,
outputChannelCount: [audio_cxt.destination.channelCount]
};
let audio_worklet = new AudioWorkletNode(audio_cxt, "synthesiser", audio_worklet_options);
audio_worklet.connect(audio_cxt.destination);
document.addEventListener("keydown", (evt) => {
for (let key = 0; key < 12; ++key) {
if (evt.code == "Key" + "QWERTYUIOPAS"[key]) {
audio_worklet.port.postMessage(a2_hertz * Math.pow(twelfth_root_of_two, key));
}
}
});
document.addEventListener("keyup", (evt) => {
audio_worklet.port.postMessage(0.0);
});
})();
syn-worklet.js
function angular_frequency(hertz) {
return hertz * 2 * Math.PI;
}
let OSC_TYPES = {"sine": 0, "square": 1, "triangle": 2};
function oscillator(hertz, osc_type) {
switch (osc_type) {
case OSC_TYPES.sine: {
return Math.sin(angular_frequency(hertz) * currentTime);
} break;
case OSC_TYPES.square: {
return Math.sin(angular_frequency(hertz) * currentTime) > 0.0 ? 1.0 : -1.0;
} break;
case OSC_TYPES.triangle: {
return Math.asin(Math.sin(angular_frequency(hertz) * currentTime)) * (2.0 / Math.PI);
} break;
default: {
return 0.0;
}
}
}
class Synthesiser extends AudioWorkletProcessor {
constructor() {
super();
this.hertz = 0.0;
this.port.onmessage = (evt) => {
this.hertz = evt.data;
};
}
process(inputs, outputs) {
let channels = outputs[0];
let num_samples_per_channel = channels[0].length;
for (let pcm_i = 0; pcm_i < num_samples_per_channel; ++pcm_i) {
let volume = 0.1;
let pcm_value = volume * oscillator(this.hertz, OSC_TYPES.sine);
for (let channel_i = 0; channel_i < channels.length; ++channel_i) {
channels[channel_i][pcm_i] = pcm_value;
}
}
return true;
}
}
registerProcessor("synthesiser", Synthesiser);
I think the problem is that currentTime seems to be the only thing which influences the output of your oscillator() function. But currentTime doesn't change during the invocation of the process() function.
I would recommend using currentFrame instead. It will give you an integer value which represents the currentTime in frames. If you combine that with pcm_i you get the actual index of the sample that you're processing.
const currentSample = currentFrame + pcm_i;
const currentSampleInSeconds = (currentFrame + pcm_i) / sampleRate;
I am building a Simon game, but I get stucked on some point.
I have the array with the currentSequence that contains random sounds from another array. I created methods and properties in order to push random sounds inthe array and play them each 0.5 seconds.
function getRandomInt(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
var game = function() {
this.power = 0;
this.start = false;
this.strict = false;
this.level = 1;
this.sounds = [
"https://s3.amazonaws.com/freecodecamp/simonSound1.mp3",
"https://s3.amazonaws.com/freecodecamp/simonSound2.mp3",
"https://s3.amazonaws.com/freecodecamp/simonSound3.mp3",
"https://s3.amazonaws.com/freecodecamp/simonSound4.mp3"
];
this.wrongMatch = "https://static.mezgrman.de/downloads/wwm/falsch.mp3";
this.currentSequence = [];
this.playerSequence = [];
this.addLevel = function() {
this.level++;
}
this.getcurrentSequence = function() {
var tot = this.level;
for (var i = 0; i <= tot; i++) {
var index = getRandomInt(0, tot);
this.currentSequence.push(this.sounds[index]);
}
console.log(this.currentSequence);
}
this.playCurrentSequence = function() {
for (var i = 0; i < this.currentSequence.length; i++) {
var audio = new Audio(this.currentSequence[i]);
audio.play();
}
}
this.reset = function() {
this.power = 0;
this.start = false;
this.strict = false;
this.level = 1;
this.currentSequence = [];
this.playerSequence = [];
}
}
var simon = new game();
$(document).ready(function() {
simon.level = 4;
simon.getcurrentSequence();
simon.playCurrentSequence();
The last 3 lines are for test.
I think I should use a setInterval but now i got stuck and i can't see the solution
Where
tl;dr jsFiddle
I can see you're new, so I rewrote your code to give you better idea on how to write this stuff. Key highlights:
Use ES6 - If you're writing a game, you don't need to use 10 years old syntax
Use audio events instead of timeouts - audio emits ended event when it's done playing. See all useful events here: Media Events - MDN
Cache created audios - once you create an audio, put it in some array. You can rewind old audio using Audio.currentTime = 0
class Game {
constructor() {
this.power = 0;
this.start = false;
this.strict = false;
this.level = 1;
/** #type {Array<string>} */
this.sounds = [
"https://s3.amazonaws.com/freecodecamp/simonSound1.mp3",
"https://s3.amazonaws.com/freecodecamp/simonSound2.mp3",
"https://s3.amazonaws.com/freecodecamp/simonSound3.mp3",
"https://s3.amazonaws.com/freecodecamp/simonSound4.mp3"
];
/** #type {Array<HTMLAudioElement>} cached audios so that we don't create them every time */
this.audios = [];
this.audios.length = this.sounds.length;
/** #type {HTMLAudioElement} */
this.currentAudio = null;
/** #type {number} timeout ID returned by setTimeout*/
this.nextSoundTimeout = -1;
this.shouldPlaySounds = true;
}
startRandomSounds() {
this.shouldPlaySounds = true;
this.soundDone();
}
stopRandomSounds() {
this.shouldPlaySounds = false;
if (this.currentAudio) {
this.currentAudio.pause();
}
}
/**
* Called when sound is done playing, next sound will start after that.
*/
soundDone() {
if (this.shouldPlaySounds) {
const soundIndex = Math.floor(this.sounds.length*Math.random());
/** #type {HTMLAudioElement} */
var audio = null;
/// Audio created alread
if (this.audios[soundIndex] instanceof HTMLAudioElement) {
audio = this.audios[soundIndex];
}
/// Audio not created so create it
else {
console.info("Create audio for sound ", this.sounds[soundIndex]);
audio = new Audio(this.sounds[soundIndex]);
/// listen when audio is done
audio.addEventListener("ended", () => { console.info("Done playing"); this.soundDone(); });
this.audios[soundIndex] = audio;
}
this.currentAudio = audio;
audio.currentTime = 0;
audio.play();
}
}
}
const game = new Game();
game.startRandomSounds();
Try you function like this:
this.playCurrentSequence = function() {
for (var i = 0; i < this.currentSequence.length; i++) {
var audio = new Audio(this.currentSequence[i]);
setTimeout(function(){audio.play();}, 500); // This is the change I did.
}
}
You can use the audio ended event so it fires as soon as the previous audio file is done. This would allow you to increase the difficulty of the game with each level and speed up the game and have the next audio fire at the correct time.
this.playCurrentSequence = function(index) {
var that = this;
let audio = new Audio(this.currentSequence[index]);
audio.onended = function(){
if(that.currentSequence.length > index)
that.playCurrentSequence(++index);
}
audio.play();
}
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
},
Click this link, after around 2 seconds you will see some ships coming at you. Now go to a different window for like 15-20 seconds. then come back, you will see a huge wall of enimes coming at you. Now obviously this is because canvas keeps the loops going but is just not letting the came continue on?
So any help would be great, I know this is not an issue during gameplay but if anyone goes off the window then it kind of messes things up...
I tried to solve this by adding these listeners:
window.onblur = function() {
// Add logic to pause the game here...
stopLoop();
};
window.onfocus = function() {
// Add logic to pause the game here...
startLoop();
};
But it does not solve the issue...
The actual loops:
function init()
{
isPlaying = true;
drawBackground();
drawBars();
setUpListeners();
startLoop();
}
and then...
function Loop()
{
if (isPlaying == true)
{
Player1.draw();
requestAnimFrame(Loop);
drawAllEnemies();
}
}
function startLoop()
{
isPlaying = true;
Loop();
startSpawningEnemies();
}
function stopLoop()
{
isPlaying = false;
stopSpawningEnemies();
}
function spawnEnemy(n) //total enemies starts at 0 and every-time you add to array
{
for (var x = 0; x < n; x++)
{
enemies[totalEnemies] = new Enemy();
totalEnemies++;
}
}
function drawAllEnemies()
{
ClearEnemyCanvas();
for(var i = 0; i < enemies.length; i++)
{
enemies[i].draw();
}
}
function startSpawningEnemies()
{
stopSpawningEnemies();
spawnInterval = setInterval(function() {spawnEnemy(spawnAmount);}, spawnRate); //this calls spawnEnemy every spawnRate
/////////spawn 'spawnAmount' enemies every 2 seconds
}
function stopSpawningEnemies()
{
clearInterval(spawnInterval);
}
Actual methods for the enemy:
function Enemy() //Object
{
//////Your ships values
this.EnemyHullMax = 1000;
this.EnemyHull = 1000;
this.EnemyShieldMax = 1000;
this.EnemyShield = 347;
this.SpaceCrystalReward = 2684;
this.EnemySpeed = 2; //should be around 6 pixels every-time draw is called by interval, directly linked to the fps global variable
////////////
////Pick Ship
this.type = "Hover";
this.srcX = EnemySrcXPicker(this.type);
this.srcY = EnemySrcYPicker(this.type);
this.enemyWidth = EnemyWidthPicker(this.type);
this.enemyHeight = EnemyHeightPicker(this.type);
this.drawX = EnemydrawXPicker(this.type);
this.drawY = EnemydrawYPicker(this.type);
////
}
Enemy.prototype.draw = function()
{
this.drawX -= this.EnemySpeed;
ctxEnemy.globalAlpha=1;
ctxEnemy.drawImage(spriteImage,this.srcX,this.srcY,this.enemyWidth,this.enemyHeight,this.drawX,this.drawY,this.enemyWidth,this.enemyHeight);
}
function EnemySrcXPicker(type)
{
if (type == "Hover")
{
return 906;
}
}
function EnemySrcYPicker(type)
{
if (type == "Hover")
{
return 616;
}
}
function EnemydrawXPicker(type)
{
if (type == "Hover")
{
return Math.floor(Math.random() * 1000) + canvasWidthEnemy;
}
}
function EnemydrawYPicker(type)
{
if (type== "Hover")
{
return Math.floor(Math.random() * (canvasHeightEnemy - 72));
}
}
function EnemyWidthPicker(type)
{
if (type == "Hover")
{
return 90;
}
}
function EnemyHeightPicker(type)
{
if (type == "Hover")
{
return 72;
}
}
Depends on the loop.
If you use setTimeOut or setInterval, then yes. The loop will continue even when the window loses focus.
If you use requestFrameAnimation, then no, the loop will stop when the window loses focus.
requestFrameAnimation was created to solve issues like this. Having you PC burn CPU cycles for something not active is just silly.