Hatena::ブログ(Diary)

aikeの日記 RSSフィード

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

もう半年以上前に作ったやつですけどせっかくなので解説。

 

JavaScriptプログラミングをやっていると人は誰しもシンセを作りたくなるかと思います。僕も3年くらい前にWeb Audio Synthというのを作りました。

 

いまはウェブブラウザオーディオMIDIAPIが充実してきてシンプルな単音のシンセを作るのは簡単になりました。でもちょっと和音を弾きたくなってポリシンセに拡張しようと思うとこれがけっこう大変だったりします。

同時発音数6ボイスのポリシンセを作るとなったら、ほんとうにモノシンセを6個分実装する必要があります。さらに、和音を弾いている最中に追加で音を重ねるときなど空いているボイスをさがして割り当てるといったオブジェクトプーリングの仕組みが必要になります。

また、いくつかシンセを作っていると、鍵盤のUIMIDI入力の対応など定型的で毎回同じ作業があることに気づいてきます。

 

そんなわけで、そういった部分をテンプレ的に用意しました。つまり、ボイス(ノートナンバーに応じて単音を生成する処理)の中身を自作すれば、それ以外の和音の処理、鍵盤のUIASCIIキーボードイベント処理、MIDIイベント処理、WebMidiLinkイベント処理などはテンプレにまかせてしまえるというものです。

https://github.com/aike/TemplateSynth

f:id:aike:20140910001405p:image

 

シンセの作成例

自分のシンセを作りたい時は 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のみだと思います。

トラックバック - http://d.hatena.ne.jp/aike/20140909