Zeke Cachi
Zeke Cachi

Reputation: 65

making a Javascript metronome

Newbie here, I'm trying to make a Javascript metronome, but I'm stuck on making the sound play according to the current bpm of the project

Here's where I think the problem lies, when I press play, it sounds, but in a fixed bpm, that is not the one being inputted in the bpm variable:

//VARIABLES
let bpm = 150;
let soundInterval = (60/bpm)*1000;


//FUNCTIONS

//START METRONOME
let startStopMetronome = startButton.addEventListener('click', () => {
    startMetronome(soundInterval);
})


function startMetronome(si) {
    setTimeout(() => {
        primaryBeat.play();
        startMetronome(si); 
    },si);
}

UPDATE: I´ve tried making the soundInterval update with a function, but it still does the same, and plays the same fixed interval no matter the bpm changes

//VARIABLES
let bpm = 150;
let soundInterval;




//FUNCTIONS

//START METRONOME
let startStopMetronome = startButton.addEventListener('click', () => {
    soundInterval = calculateSoundInterval();
    startMetronome(soundInterval);
})


function startMetronome(si) {
    setTimeout(() => {
        primaryBeat.play();
        startMetronome(si); 
    },si);
}

let calculateSoundInterval = () => {
    return (60/bpm)*1000;
}

let updateBpmInDisplay = display.addEventListener('change', ()=> {
    soundInterval = calculateSoundInterval();
})

Upvotes: 1

Views: 1076

Answers (2)

Zeke Cachi
Zeke Cachi

Reputation: 65

Thanks everyone for the help. At the end, the issue was that I had not configured the inputs to reset the sound on click or input, so the sound just played until it stopped naturally. I added currentTime = 0, and problem solved, now it works as intended:

function startMetronome(si) {
    timerId = setInterval(() => {
        primaryBeat.play();
        primaryBeat.currentTime = 0;
    },si);
}

Upvotes: 0

async await
async await

Reputation: 2395

I thought it might be fun to look over how I would accomplish this. I used an AudioContext and oscillator to make the sound, and instead of using setTimeout directly I used a helper function to model async practice. If I wanted to improve upon this, I might hold onto my timeout in a higher scope, so when stop is pressed I could clear the timeout and prevent any bugs when pressing stop and start quickly to have two loops running at the same time. I hope my take on things has some value 👍 was fun to look at.

const get = str => document.querySelector(str);
const wait = seconds => new Promise(r => setTimeout(r, seconds * 1e3));

const context = new AudioContext();   
let cntr = 0;
const makeSound = () => {
  const sound = context.createOscillator();
  const fourthBeat = cntr++ % 4 === 0;
  sound.frequency.value = fourthBeat ? 400 : 440;
  sound.connect(context.destination);
  sound.start(context.currentTime);
  sound.stop(context.currentTime + .1);
};

let bpm = 60;
get("input").addEventListener("input", e => {
  bpm = e.target.value;
  get(".display").innerText = bpm;
});

let running = true;
get(".start").addEventListener("click", async () => {
  running = true;
  get(".start").disabled = true;
  while (running) {
    makeSound();    
    await wait(60 / bpm);
  }
});

get(".stop").addEventListener("click", () => {
  running = false
  get(".start").disabled = false;
  cntr = 0;
});
<div class="display">60</div>
<input min="30" max="200" value="60" type="range" />
<button class="start">start</button>
<button class="stop">stop</button>

Upvotes: 1

Related Questions