Ejemplo de metrónomo en Javascript

5 minutos

Metrónomo

A continuación puedes ver un minimalista y sencillo metrónomo en JavaScript plano (Vainilla) adaptado a smartphone o escritorio. Con la ayuda de la etiqueta <audio> verás que es posible reproducir el sonido.

DEMO

A continuación puedes jugar un poco con el metrónomo.

See the Pen Metronome in JavaScript by Andros Fenollosa (@androsfenollosa) on CodePen.

Audios

Deberas tener un audio de tick típico de un metrónomo. Puedes descargar el que uso en el ejemplo en formato mp3 y ogg. Colócalos a la altura del HTML.

Tick en mp3

Tick en ogg

HTML

En el ejempo uso Bootstrap 5, pero eres libre de dar el CSS que más se adapte a tu diseño quitando mis clases.

<!doctype html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>Metronomo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
</head>
<body>
    <section id="metronomo" class="text-center w-25 mt-5 mx-auto">
        <!-- Informacion -->
        <h1 id="ppm"></h1>
        <h2>PPM</h2>
        <!-- Controles -->
        <div>
            <p class="d-flex justify-content-between gap-1">
                <!-- Botones para variar los PPM (Pulsaciones por minutos) -->
                <button id="boton-decrecer-5-ppm" class="btn btn-secondary flex-grow-1">-5</button>
                <button id="boton-decrecer-1-ppm" class="btn btn-secondary flex-grow-1">-</button>
                <button id="boton-crecer-1-ppm" class="btn btn-secondary flex-grow-1">+</button>
                <button id="boton-crecer-5-ppm" class="btn btn-secondary flex-grow-1">+5</button>
            </p>
            <p>
                <!-- Boton para iniciar o pausar -->
                <button id="boton-reproducir" class="d-block btn btn-primary w-100">Empezar</button>
            </p>
        </div>
        <!-- Reproductor (será invisible) -->
        <audio id="audio-metronomo">
            <!-- Para conseguir la mayor compatibilidad, se usa tanto OGG como MP3 -->
            <source src="tick.ogg" type="audio/ogg">
            <source src="tick.mp3" type="audio/mpeg">
        </audio>
    </section>
</body>
</html>

JavaScript

Descubrirás que he comentado cada apartado. No contiene ninguna dificultad destacable.

//===
// METRÓNOMO
// Author: https://programadorwebvalencia.com/Javascript-ejemplo-metronomo/
//===

//---
// Variables
//---
let reproduciendo = false;
let PPM = 50;
let intervalo = undefined;
const textoReproduciendose = "Empezar";
const textoParado = "Pausar";
const tituloPPM = document.querySelector('#ppm');
const botonDecrecer5PPM = document.querySelector('#boton-decrecer-5-ppm');
const botonDecrecer1PPM = document.querySelector('#boton-decrecer-1-ppm');
const botonCrecer1PPM = document.querySelector('#boton-crecer-1-ppm');
const botonCrecer5PPM = document.querySelector('#boton-crecer-5-ppm');
const botonReproducir = document.querySelector('#boton-reproducir');
const audioMetronomo = document.querySelector('#audio-metronomo');

//---
// Funciones
//---


/**
 * Dibuja los cambios
 *
 * @param {number} ppm Pulsaciones por Minuto
 * @param {boolean} isPlay ¿Esta reproduciendose?
 * @return {boolean} Se ha renderizado
 */
function renderizarCambios(ppm, isPlay) {
    // Texto PPM
    tituloPPM.textContent = ppm;
    // Texto boton reproducir
    botonReproducir.textContent = reproduciendo ? textoParado : textoReproduciendose;

    return true;
}

/**
 * Dibuja los cambios
 *
 * @param {number} ppm Pulsaciones por Minuto
 * @param {HTMLAudioElement} audio
 * @param {boolean} isPlay ¿Esta reproduciendose?
 * @param {intervalID} intervaloActual Intervalo
 * @return {intervalID} Intervalo nuevo
 */
function reproducirOPausar(ppm, audio, isPlay, intervaloActual) {
    let miIntervalo;
    // Para intervalo
    clearInterval(intervaloActual);
    // Empieza intervalo nuevo
    if (isPlay) {
        miIntervalo = setInterval(function() {
            // Reproduce audio
            audio.play();
        }, PPMToMiliseconds(ppm));
    }
    return miIntervalo;
}

/**
 * Transforma los PPM a milesimas
 *
 * @param {number} ppm
 * @return {number} Milesimas
 */
function PPMToMiliseconds(ppm) {
    return (60 / ppm) * 1000;
}

/**
 * Decrecer PPM
 *
 * @param {number} actualPPM
 * @param {number} cantidad
 * @return {number} Resultado
 */
function decrecerPPM(actualPPM, cantidad=1) {
    const resultado = actualPPM - cantidad;
    return resultado < 0 ? 0 : resultado;
}

/**
 * Aumentar PPM
 *
 * @param {number} actualPPM
 * @param {number} cantidad
 * @return {number} Resultado
 */
function crecerPPM(actualPPM, cantidad=1) {
    return actualPPM + cantidad;
}

/**
 * Evento reproduce audio
 *
 * @param {event}
 */
function eventoReproducir(event) {
    // Inicia o pausa
    reproduciendo = !reproduciendo;
    // Reproduce
    intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
    renderizarCambios(PPM, reproduciendo);
}

/**
 * Evento decrecer 5 PPPM
 *
 * @param {event}
 */
function eventoDecrecer5PPM(event) {
    PPM = decrecerPPM(PPM, 5);
    intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
    renderizarCambios(PPM, reproduciendo);
}

/**
 * Evento decrecer 1 PPPM
 *
 * @param {event}
 */
function eventoDecrecer1PPM(event) {
    PPM = decrecerPPM(PPM);
    intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
    renderizarCambios(PPM, reproduciendo);
}

/**
 * Evento crecer 1 PPPM
 *
 * @param {event}
 */
function eventoCrecer1PPM(event) {
    PPM = crecerPPM(PPM);
    intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
    renderizarCambios(PPM, reproduciendo);
}

/**
 * Evento crecer 5 PPPM
 *
 * @param {event}
 */
function eventoCrecer5PPM(event) {
    PPM = crecerPPM(PPM, 5);
    intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
    renderizarCambios(PPM, reproduciendo);
}

//---
// Eventos
//---
botonDecrecer5PPM.addEventListener('click', eventoDecrecer5PPM);
botonDecrecer1PPM.addEventListener('click', eventoDecrecer1PPM);
botonCrecer1PPM.addEventListener('click', eventoCrecer1PPM);
botonCrecer5PPM.addEventListener('click', eventoCrecer5PPM);
botonReproducir.addEventListener('click', eventoReproducir);

//---
// Inicio
//---
renderizarCambios(PPM, reproduciendo);

Completo

Todo unido quedaría de la siguiente manera. ¡No olvides descargar los audios!

<!doctype html>
<html lang="es">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <title>Metronomo</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
    <script>
        document.addEventListener('DOMContentLoaded', function () {
          //===
          // METRÓNOMO
          // Author: https://programadorwebvalencia.com/Javascript-ejemplo-metronomo/
          //===

          //---
          // Variables
          //---
          let reproduciendo = false;
          let PPM = 50;
          let intervalo = undefined;
          const textoReproduciendose = "Empezar";
          const textoParado = "Pausar";
          const tituloPPM = document.querySelector('#ppm');
          const botonDecrecer5PPM = document.querySelector('#boton-decrecer-5-ppm');
          const botonDecrecer1PPM = document.querySelector('#boton-decrecer-1-ppm');
          const botonCrecer1PPM = document.querySelector('#boton-crecer-1-ppm');
          const botonCrecer5PPM = document.querySelector('#boton-crecer-5-ppm');
          const botonReproducir = document.querySelector('#boton-reproducir');
          const audioMetronomo = document.querySelector('#audio-metronomo');

          //---
          // Funciones
          //---


          /**
           * Dibuja los cambios
           *
           * @param {number} ppm Pulsaciones por Minuto
           * @param {boolean} isPlay ¿Esta reproduciendose?
           * @return {boolean} Se ha renderizado
           */
          function renderizarCambios(ppm, isPlay) {
              // Texto PPM
              tituloPPM.textContent = ppm;
              // Texto boton reproducir
              botonReproducir.textContent = reproduciendo ? textoParado : textoReproduciendose;

              return true;
          }

          /**
           * Dibuja los cambios
           *
           * @param {number} ppm Pulsaciones por Minuto
           * @param {HTMLAudioElement} audio
           * @param {boolean} isPlay ¿Esta reproduciendose?
           * @param {intervalID} intervaloActual Intervalo
           * @return {intervalID} Intervalo nuevo
           */
          function reproducirOPausar(ppm, audio, isPlay, intervaloActual) {
              let miIntervalo;
              // Para intervalo
              clearInterval(intervaloActual);
              // Empieza intervalo nuevo
              if (isPlay) {
                  miIntervalo = setInterval(function() {
                      // Reproduce audio
                      audio.play();
                  }, PPMToMiliseconds(ppm));
              }
              return miIntervalo;
          }

          /**
           * Transforma los PPM a milesimas
           *
           * @param {number} ppm
           * @return {number} Milesimas
           */
          function PPMToMiliseconds(ppm) {
              return (60 / ppm) * 1000;
          }

          /**
           * Decrecer PPM
           *
           * @param {number} actualPPM
           * @param {number} cantidad
           * @return {number} Resultado
           */
          function decrecerPPM(actualPPM, cantidad=1) {
              const resultado = actualPPM - cantidad;
              return resultado < 0 ? 0 : resultado;
          }

          /**
           * Aumentar PPM
           *
           * @param {number} actualPPM
           * @param {number} cantidad
           * @return {number} Resultado
           */
          function crecerPPM(actualPPM, cantidad=1) {
              return actualPPM + cantidad;
          }

          /**
           * Evento reproduce audio
           *
           * @param {event}
           */
          function eventoReproducir(event) {
              // Inicia o pausa
              reproduciendo = !reproduciendo;
              // Reproduce
              intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
              renderizarCambios(PPM, reproduciendo);
          }

          /**
           * Evento decrecer 5 PPPM
           *
           * @param {event}
           */
          function eventoDecrecer5PPM(event) {
              PPM = decrecerPPM(PPM, 5);
              intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
              renderizarCambios(PPM, reproduciendo);
          }

          /**
           * Evento decrecer 1 PPPM
           *
           * @param {event}
           */
          function eventoDecrecer1PPM(event) {
              PPM = decrecerPPM(PPM);
              intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
              renderizarCambios(PPM, reproduciendo);
          }

          /**
           * Evento crecer 1 PPPM
           *
           * @param {event}
           */
          function eventoCrecer1PPM(event) {
              PPM = crecerPPM(PPM);
              intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
              renderizarCambios(PPM, reproduciendo);
          }

          /**
           * Evento crecer 5 PPPM
           *
           * @param {event}
           */
          function eventoCrecer5PPM(event) {
              PPM = crecerPPM(PPM, 5);
              intervalo = reproducirOPausar(PPM, audioMetronomo, reproduciendo, intervalo);
              renderizarCambios(PPM, reproduciendo);
          }

          //---
          // Eventos
          //---
          botonDecrecer5PPM.addEventListener('click', eventoDecrecer5PPM);
          botonDecrecer1PPM.addEventListener('click', eventoDecrecer1PPM);
          botonCrecer1PPM.addEventListener('click', eventoCrecer1PPM);
          botonCrecer5PPM.addEventListener('click', eventoCrecer5PPM);
          botonReproducir.addEventListener('click', eventoReproducir);

          //---
          // Inicio
          //---
          renderizarCambios(PPM, reproduciendo);

        });
    </script>
</head>
<body>
    <section id="metronomo" class="text-center w-25 mt-5 mx-auto">
        <!-- Informacion -->
        <h1 id="ppm"></h1>
        <h2>PPM</h2>
        <!-- Controles -->
        <div>
            <p class="d-flex justify-content-between gap-1">
                <!-- Botones para variar los PPM (Pulsaciones por minutos) -->
                <button id="boton-decrecer-5-ppm" class="btn btn-secondary flex-grow-1">-5</button>
                <button id="boton-decrecer-1-ppm" class="btn btn-secondary flex-grow-1">-</button>
                <button id="boton-crecer-1-ppm" class="btn btn-secondary flex-grow-1">+</button>
                <button id="boton-crecer-5-ppm" class="btn btn-secondary flex-grow-1">+5</button>
            </p>
            <p>
                <!-- Boton para iniciar o pausar -->
                <button id="boton-reproducir" class="d-block btn btn-primary w-100">Empezar</button>
            </p>
        </div>
        <!-- Reproductor (será invisible) -->
        <audio id="audio-metronomo">
            <!-- Para conseguir la mayor compatibilidad, se usa tanto OGG como MP3 -->
            <source src="tick.ogg" type="audio/ogg">
            <source src="tick.mp3" type="audio/mpeg">
        </audio>
    </section>
</body>
</html>

Espero que sea útil en cada tick.

Esta obra está bajo una Licencia Creative Commons Atribución-NoComercial-SinDerivadas 4.0 Internacional.

Atribución/Reconocimiento-NoComercial-SinDerivados 4.0 Internacional

Donación con recompensa

  • 1 café: Respondo a tu duda en los comentarios.
  • 2 cafés: Respondo en menos de 24h a tu comentario.
  • 3 cafés: Todo lo anterior y además te doy las gracias en mis redes.
Comprame un café

Comentarios

{{ comments.length }} comentarios

Nuevo comentario

Nueva replica  {{ formatEllipsisAuthor(replyComment.author) }}

Acepto la política de Protección de Datos.

Escribe el primer comentario

Tal vez también te interese...