本稿は「ml5-examples/p5js/PitchDetection/PitchDetection_Piano」サンプルの解説です。
下記リンクをクリックすると、実際の動作が確認できます。このサンプルを試すには、
-
- マイクを接続しておき、リンク先を開きます。
- ブラウザがマイクへのアクセス許可を求めてくるので、[許可]をクリックします。
- ブラウザ画面のどこかをクリックします。動作しない場合は再ロードします。
- 鼻歌を歌うと、画面に周波数とMIDI番号、音程が表示され、その音に相当する鍵盤がグレーで表示されます。
音程のCというのは”ド”です。Dは”レ”、Eは”ミ”、Fは”ファ”、Gは”ソ”、Aは”ラ”、Bは”シ”です。
HTML
<h1>ピッチ抽出ピアノ</h1>
<p id='status'>画面をクリック</p>
<p>鼻歌やハミング、口笛、楽器の演奏などで音を出し、抽出器にピッチを推測させる。</p>
<p>周波数:<span id="freq">0</span></p>
<p>MIDI番号:<span id="midi"></span></p>
<p>音程(C,D,Eなど):<span id="cde"></span></p>
<script src="sketch.js?=2"></script>
sketch.js
let pitch;
let audioContext;
let audioStream;
// 鍵盤に関係する変数
const cornerCoords = [10, 40];
const rectWidth = 90;
const rectHeight = 300;
const keyRatio = 0.58;
const scale = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
let currentNote = '';
function setup() {
createCanvas(640, 520);
// 1) マイクからオーディオ入力(AudioIn)を作成し、マイクをオンにする。
mic = new p5.AudioIn();
mic.start();
}
// 3) ml5.pitchDetection()でAudioContext、マイク、モデルを関連付ける。
function startPitch() {
pitch = ml5.pitchDetection('./model/', audioContext, mic.stream, modelLoaded);
}
// 4)モデルが読み込まれたらgetPitching()を呼び出す。
function modelLoaded() {
select('#status').html('モデルが読み込まれた');
getPitching();
}
// 5) ピッチの値を得る
function getPitching() {
pitch.getPitch(function(err, frequency) {
if (frequency) {
// 与えられた周波数に最も近いMIDIノートを返す
// https://p5js.org/reference/#/p5/freqToMidi
// http://www.asahi-net.or.jp/~HB9T-KTD/music/Japan/Research/DTM/freq_map.html
let midiNum = freqToMidi(frequency);
// そのMIDIノートに対応する今の音名(CからBまでの12個)
currentNote = scale[midiNum % 12];
select('#freq').html(frequency);
select('#midi').html(midiNum);
select('#cde').html(currentNote);
}
getPitching();
})
}
// 描画をつづける
function draw() {
drawKeyboard();
}
// 今の音の鍵盤を塗りながら、鍵盤全体を描く
function drawKeyboard() {
let whiteKeyCounter = 0;
// 背景と線
background(255);
strokeWeight(2);
stroke(50);
// 白鍵
for (let i = 0; i < scale.length; i++) {
if (scale[i].indexOf('#') == -1) {
if (scale[i] == currentNote) {
fill(200);
}
else {
fill(255);
}
rect(cornerCoords[0] + (whiteKeyCounter * rectWidth), cornerCoords[1], rectWidth, rectHeight);
whiteKeyCounter++;
}
}
whiteKeyCounter = 0;
// 黒鍵
for (let i = 0; i < scale.length; i++) {
if (scale[i].indexOf('#') > -1) {
if (scale[i] == currentNote) {
fill(100);
}
else {
fill(0);
}
rect(cornerCoords[0] + (whiteKeyCounter * rectWidth) - (rectWidth / 3), cornerCoords[1], rectWidth * keyRatio, rectHeight * keyRatio);
}
else {
whiteKeyCounter++;
}
}
}
// オーディオの自動再生をブロックするChromeへの対処
// 2) ユーザーの画面クリックでAudioContextオブジェクトを取得し、処理をスタート
function touchStarted() {
// AudioContextオブジェクトを得る
if (getAudioContext().state !== 'running') {
audioContext = getAudioContext();
// あらかじめ中断させられた音声コンテキストの時間の進行を返す。
audioContext.resume();
// ここから本格スタート
startPitch();
}
}