Webでポリシンセ作るときのテンプレ作った

もう半年以上前に作ったやつですけどせっかくなので解説。
 
JavaScriptでプログラミングをやっていると人は誰しもシンセを作りたくなるかと思います。僕も3年くらい前にWeb Audio Synthというのを作りました。
 
いまはウェブブラウザもオーディオやMIDIAPIが充実してきてシンプルな単音のシンセを作るのは簡単になりました。でもちょっと和音を弾きたくなってポリシンセに拡張しようと思うとこれがけっこう大変だったりします。
同時発音数6ボイスのポリシンセを作るとなったら、ほんとうにモノシンセを6個分実装する必要があります。さらに、和音を弾いている最中に追加で音を重ねるときなど空いているボイスをさがして割り当てるといったオブジェクトプーリングの仕組みが必要になります。
また、いくつかシンセを作っていると、鍵盤のUIやMIDI入力の対応など定型的で毎回同じ作業があることに気づいてきます。
 
そんなわけで、そういった部分をテンプレ的に用意しました。つまり、ボイス(ノートナンバーに応じて単音を生成する処理)の中身を自作すれば、それ以外の和音の処理、鍵盤のUI、ASCIIキーボードイベント処理、MIDIイベント処理、WebMidiLinkイベント処理などはテンプレにまかせてしまえるというものです。

https://github.com/aike/TemplateSynth

 
■シンセの作成例
自分のシンセを作りたい時は js/voice.js を拡張するようにしてください。

メソッド名 処理内容
Voice#noteOn(note, velocity) 発音開始
Voice#changeNote(note) 発音中に音程変更
Voice#noteOff() 発音終了
Voice#setParam(param_id, val) パラメータ設定(空でも可)

 
デフォルトではサイン波を鳴らすシンセになっています。

var Voice = function(ctx) {
	this.ctx = ctx;
	this.next_node = null;
	this.noteNoToFrequency = function(noteno) {
		return 440.0 * Math.pow(2.0, (noteno - 69.0) / 12.0); 
	}
}

Voice.prototype.noteOn = function(note, velocity) {
	this.osc = this.ctx.createOscillator();
	this.osc.frequency.value = this.noteNoToFrequency(note);
	this.osc.connect(this.next_node);
	this.osc.start(0);
}

 
こんな風に変えるとプレイバックサンプラーになります。

var Voice = function(ctx) {
	this.ctx = ctx;
	this.next_node = null;
	this.noteNoToSpeed = function(noteno) {
		return Math.pow(2.0, (noteno - 69.0) / 12.0); 
	}
	var self = this;
	this.loadwav('sampler.wav', function(buf) { self.buf = buf; });
}

Voice.prototype.loadwav = function(file, callback) {
	var xhr = new XMLHttpRequest();
	xhr.open("GET", file, true);
	xhr.responseType = "arraybuffer";
	var self = this;
	xhr.onload = function() {
		self.ctx.decodeAudioData(xhr.response,function(buf){
			callback(buf);
		}, function(){});
	};
	xhr.send();
}

Voice.prototype.noteOn = function(note, velocity) {
	this.sample = this.ctx.createBufferSource();
	this.sample.buffer = this.buf;
	this.sample.playbackRate.value = this.noteNoToSpeed(note);
	this.sample.connect(this.next_node);
	this.sample.start(0);
}

 
1ボイスにつきデチューンした5個のノコギリ波を鳴らすSuper Saw的なシンセならこんな感じ。

var Voice = function(ctx) {
	this.ctx = ctx;
	this.next_node = null;
	this.noteNoToFrequency = function(noteno) {
		return 440.0 * Math.pow(2.0, (noteno - 69.0) / 12.0); 
	}
	this.osc = new Array(5);
	this.gain = new Array(5);
	for (var i = 0; i < 5; i++) {
		this.gain[i] = this.ctx.createGain();
		this.gain[i].gain.value = 0.3;
	}
}

Voice.prototype.noteOn = function(note, velocity) {
	for (var i = 0; i < 5; i++) {
		this.osc[i] = this.ctx.createOscillator();
		this.osc[i].type = 'sawtooth';
		this.osc[i].detune.value = 10 * i - 20;
		this.osc[i].frequency.value = this.noteNoToFrequency(note);
		this.osc[i].connect(this.gain[i]);
		this.osc[i].start(0);
	}
}

 
最大同時発音数は synth.js で以下のように指定しています。

synth = new Synth(8);

 
MIDI鍵盤から演奏したり、ASCIIキーボードを鍵盤代わりに弾くこともできます。
シンセの発音アルゴリズムを試したりするのに便利なので使ってみてください。
Web Componentsとか使ってるので動作環境は現状Chromeのみだと思います。