0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

JavaScriptで簡易メトロノーム

Last updated at Posted at 2021-06-08

####スクリーンショット####

スクリーンショット 2021-06-08 09.jpg

設置デモ
(デモ版ではテンポ変更にJogShuttleUIを使用しています)

####スクリプト####

index.html
<!DOCTYPE html>
<html lang='ja'>
<head>
<meta charset='utf-8'>
<meta name='viewport' content='user-scalable=no,width=device-width,initial-scale=1'>
<title>metronome</title>
<style>
#container {
  position: relative;
  box-sizing: border-box;
  left: 0;
  width: 300px;
  height: 360px;
}
input[type=number] {
  width: 50px;
}
#st {
  position: absolute;
  box-sizing: border-box;
  top: 60%;
  left: 48%;
  width: 4%;
  height: 70%;
  border: 1px solid #444;
  background-color: #ccc;
  border-radius: 3px;
  transform: rotate(0deg) translateY(-55%);
}
#sg {
  position: absolute;
  right: 0;
  bottom: 0;
}
#sg [id*=sg] {
  position: relative;
  display: inline-block;
  border: 1px solid gray;
  border-radius: 40%;
  width: 20px;
  height: 20px;
}
.control {
  position: fixed;
  top: 8px;
  left: 8px;
}
</style>
</head>
<body>
<div id='container'>
  <div id='st'></div>
  <div id='sg'>
    <div id='sg0'></div>
    <div id='sg1'></div>
  </div>
</div>
<div class='control'>
  <div id='control'>
    TIMING: <input type='range' id='adjust' min='0' max='1' value='0.5' step='0.025'><br>
    BEATS: <span id='vb'>0</span> <input type='range' min='0' max='8' value='0' id='beat'><br>
    TEMPO: <input type='number' min='10' max='800' value='120' id='tempo' oninput='tn.value=this.value'>BPM
    <select id='tn'></select><br>
    <button id='s'>START</button><br>
  </div>
  <button onclick='sh(this)'></button>
</div>

<script>
'use strict';
const btn = document.getElementById('s');
const st = document.getElementById('st');
const tempo = document.getElementById('tempo');
const adjust = document.getElementById('adjust');
const beat = document.getElementById('beat');
const tn = document.getElementById('tn');
const sa = 35;

let start = 0;
let status = 0;
let _count = 0;

btn.addEventListener('click', () => {
  se('blank');
  start = Date.now();
  if(status) status = 0;
  else {
    status = 1;
    loop();
  }
  btn.textContent = ['START', 'STOP'][status];
});

function loop() {
  const t = status ? (Date.now() - start) / 60000 : 0;
  const r = Math.sin(t * tempo.value * Math.PI) * sa;
  st.style.transform = `rotate(${r}deg) translateY(-55%)`;
  const count = Math.floor(t * tempo.value + +adjust.value);
  if(count !== _count) {
    _count = count;
    blink('sg1', '#8cf');
    se('click');
    if(beat.value > 0 && (count - 1) % beat.value === 0) {
      blink('sg0', '#48f');
      se('bell');
    }
  }
  if(status) requestAnimationFrame(loop);
}

function blink(id, color) {
  const d = document.getElementById(id);
  d.style.backgroundColor = color;
  setTimeout(() => {
    d.style.backgroundColor = '';
  }, 100);
}

beat.addEventListener('input', () => {
  document.getElementById('vb').textContent = beat.value;
});

function sh(e) {
  const d = document.getElementById('control');
  if(d.dataset.hide === undefined) {
    d.dataset.hide = true;
    d.style.display = 'none';
    e.textContent = '';
  }
  else {
    delete d.dataset.hide;
    d.style.display = 'block';
    e.textContent = '';
  }
}

const seList = {
  'click': './click.mp3',
  'bell' : './bell.mp3',
  'blank': './blank.mp3',
};
const seElements = {};

let context;
let webSoundApiFlg = 0;

window.AudioContext = window.AudioContext || window.webkitAudioContext;
if(window.AudioContext !== undefined) {
  context = new AudioContext();
}

const getAudioBuffer = (url, fn) => {  
  const req = new XMLHttpRequest();
  req.responseType = 'arraybuffer';
  req.onreadystatechange = () => {
    if (req.readyState === 4) {
      if (req.status === 0 || req.status === 200) {
        context.decodeAudioData(req.response, buffer => {
          fn(buffer);
        });
      }
    }
  };
  req.open('GET', url, true);
  req.send('');
};

const playSound = buffer => {
  const source = context.createBufferSource();
  source.buffer = buffer;
  source.connect(context.destination);
  source.start(0);
};

function waOnload() {
  if(context !== undefined) {
    for(let i in seList) {
      getAudioBuffer(seList[i], buffer => {
        const bufId = i;
        document.querySelector('body').appendChild(document.createElement('div'));
        document.querySelector('body>div:last-of-type').id = bufId;
        seElements[bufId] = document.getElementById(bufId);
        seElements[bufId].onclick = () => {
          playSound(buffer);
        };
      });
    }
    webSoundApiFlg = 1;
  }
}
window.addEventListener('load', waOnload);

if(window.AudioContext === undefined) {
  for(let i in seList) {
      document.querySelector('body').insertAdjacentHTML('beforeend',
          '<audio id="' + i + '" preload="auto">'+
          '   <source src="' + seList[i] + '" type="audio/mp3">'+
          '</audio>'
      );
      seElements[i] = document.getElementById(i);
  }
}

function se(id) {
  const d = seElements[id];
  if(webSoundApiFlg) {
    d.onclick();
  }
  else {
    if(id) {
      if(d.currentTime != 0 || !d.paused) {
        d.pause();
        d.currentTime = 0;
      }
      d.play();
    }
  }
}

window.addEventListener('DOMContentLoaded', () => {
  const l = [
    [40, 'Grave'],
    [46, 'Largo'],
    [52, 'Lento'],
    [58, 'Adagio'],
    [60, 'Larghetto'],
    [66, 'Andante'],
    [76, 'Andantino'],
    [84, 'Moderato'],
    [108, 'Allegretto'],
    [120, "All'o Moderato"],
    [132, 'Allegro'],
    [144, "All'o Assai"],
    [152, "All'o Vivace"],
    [160, 'Vivace'],
    [184, 'Presto'],
    [200, 'Prestissimo'],
  ];
  for(const i of l) tn.insertAdjacentHTML('beforeend', `<option value='${i[0]}'>${i[0]} : ${i[1]}</option>`);
  tn.value = tempo.value;
  tn.addEventListener('change', () => {
    tempo.value = tn.value;
  });
});
</script>
</body>
</html>

以下のmp3ファイルを使用しています。
click.mp3 クリック音
bell.mp3 ベル音
blank.mp3 無音


0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?