AndroidやiOSアプリの「ボーカル音程モニター」にヒントを得て、「鼻歌音程モニター」なるものを試作しました。
これはピッチやMIDI番号、音程の取得までは前の「5_1_2:ピッチ抽出ピアノ」と同じで、ピッチの数値を折れ線グラフで描いたものです。
グラフの描画はp5.jsのライブラリの「grafica.js」で行っています。
次のリンクをクリックすると「鼻歌音程モニター」サンプルが開きます。
HTMLではgrafica.jsを読み込んでおきます。
<script src="lib/grafica.min.js"></script>
...
<h1>鼻歌音程モニター</h1>
<p id="status">画面をクリック!</p>
<p>ピッチ:<span id=freq>...</span></p>
<p>MIDI:<span id=midi>...</span></p>
<p>音程:<span id=cde>...</span></p>
<canvas id="sketch_canvas"></canvas>
<script src="sketch.js"></script>
sketch.js
const pitchGraph = (p) => {
let point;
const points = [];
let plot;
let count = 0;
let pitch;
let audioContext;
let mic;
const scale = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'];
p.setup = function() {
const cnv = p.createCanvas(800, 500);
cnv.position(20, 240);
p.background(255);
// grafica.jsのGPoltインスタンス
plot = new GPlot(p);
// グラフの位置とサイズ、線の色と丸の色を設定
plot.setPos(5, 5);
plot.setDim(750, 300);
plot.setLineColor(p.color(255, 0, 0));
plot.setPointColor(p.color(100, 100, 255, 10));
// 軸ラベルとタイトルの設定
plot.getXAxis().setAxisLabelText("時間");
plot.getYAxis().setAxisLabelText("周波数");
plot.setTitleText("鼻歌音程モニター");
mic = new p5.AudioIn();
mic.start();
}
// 毎フレーム自動的に呼び出される
p.draw = function() {
// グラフを描画
plot.defaultDraw();
}
p.touchStarted = function() {
if (p.getAudioContext().state !== 'running') {
audioContext = p.getAudioContext();
audioContext.resume();
startPitch();
}
}
function startPitch() {
pitch = ml5.pitchDetection('./model/', audioContext, mic.stream, p.modelLoaded);
}
p.modelLoaded = function() {
console.log('モデルが読み込まれた')
p.select('#status').html('モニタリング中...');
p.getPitching();
}
p.getPitching = function() {
pitch.getPitch(function(err, frequency) {
if (frequency) {
// MIDI番号と今の音を表示
const midiNum = p.freqToMidi(frequency);
const currentNote = scale[midiNum % 12];
p.select('#freq').html(frequency);
p.select('#midi').html(midiNum);
p.select('#cde').html(currentNote);
// グラフに描画する点をGPointオブジェクトで作成
// x値がcount、y値がピッチ値
point = new GPoint(count, frequency);
}
else {
point = new GPoint(count, 0);
}
// GPointオブジェクトを配列に追加。
points.push(point)
// グラフの点に設定。
plot.setPoints(points);
count++;
p.getPitching();
})
}
}
const sketch = new p5(pitchGraph, "sketch__canvas");
/*
var p5 = function(sketch, node, sync) {
...
}
*/
最後のnew p5()はp5クラスのコンストラクタです。p5.jsには次のように書かれています。
p5インスタンスは、p5スケッチに関連するすべてのプロパティとメソッドを保持する。コンストラクタは、入ってくるsketchクロージャを期待する。また生成されたp5キャンバスを割り当てる、オプションのnodeパラメータを取ることもできる。sketchクロージャは、新しく作成されたp5インスタンスを唯一の引数として取り、そこではスケッチを実行するための、preload()やsetup()、draw()などが設定できる。
p5スケッチはグローバルモードとインスタンスモードで実行できる。
グローバル – すべてのプロパティとメソッドはwindowに割り当てられる。
インスタンス – すべてのプロパティとメソッドはp5オブジェクトに関連付けられる。@class p5
@constructor
@param {function} sketch オプションでp5インスタンス上にpreload()やsetup()、draw()プロパティなどが設定できるクロージャ,
@param {HTMLElement|Boolean} [node] キャンバスを割り当てるHTML要素。ブール値が渡された場合にはsyncとして使用する。
@param {Boolean} [sync] 同期処理で開始する(オプション)
@return {p5} p5インスタンスvar p5 = function(sketch, node, sync) { if (typeof node === 'boolean' && typeof sync === 'undefined') { sync = node; node = undefined; }
つまり、new p5(pitchGraph, “sketch__canvas”)によって、pitchGraph()関数に記述した機能を持ち、キャンバスとして<canvas id="sketch_canvas"></canvas>を使用するp5インスタンスを作成している訳です。pitchGraph()関数内では、引数として渡される変数pで、(アドオンも含む)p5.jsのプロパティやメソッドにアクセスできます。