5_1_2:ピッチ抽出ピアノ ml5.js JavaScript

本稿は「ml5-examples/p5js/PitchDetection/PitchDetection_Piano」サンプルの解説です。

下記リンクをクリックすると、実際の動作が確認できます。このサンプルを試すには、

    1. マイクを接続しておき、リンク先を開きます。
    2. ブラウザがマイクへのアクセス許可を求めてくるので、[許可]をクリックします。
    3. ブラウザ画面のどこかをクリックします。動作しない場合は再ロードします。
    4. 鼻歌を歌うと、画面に周波数と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();
    }
}

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

CAPTCHA