Last active
December 28, 2015 12:29
-
-
Save fukuroder/7501079 to your computer and use it in GitHub Desktop.
FM synthesis test using Web Audio API (Google Chrome only) >>> http://fukuroder.sakura.ne.jp/gist/7501079/fm_synthesis_test.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset='utf-8'> | |
<title>fm synthesis test</title> | |
<script src='http://code.jquery.com/jquery-2.1.1.min.js'></script> | |
<script> | |
$(function(){ | |
//-------------- | |
// AudioContext | |
//-------------- | |
var context = new AudioContext(); | |
//----------- | |
// FM Matrix | |
//----------- | |
var fm_arr = new Array(36); | |
$('.matrix_elm') | |
.attr({max:100, min:0, value:0}) | |
.css({width:'58px'}) | |
.change(function(){ | |
// find index | |
var idx = $('.matrix_elm').index($(this)); | |
var val = parseFloat(this.value); | |
// element index | |
// 00 01 02 03 04 05 | |
// 06 07 08 09 10 11 | |
// 12 13 14 15 16 17 | |
// 18 19 20 21 22 23 | |
// 24 25 26 27 28 29 | |
// 30 31 32 33 34 35 | |
if(idx%7 == 0){ | |
// 00 07 14 21 28 35 | |
fm_arr[idx] = val*val/9600.0; | |
} | |
else{ | |
// otherwise | |
fm_arr[idx] = val*val/4800.0; | |
} | |
}); | |
//------- | |
// Ratio | |
//------- | |
var freq = 0.0; | |
var dt_arr = new Array(6); | |
$('.ratio_elm') | |
.attr({max:64, min:0, step:0.01, value:1}) | |
.css({width:'58px'}) | |
.change(function(){ | |
// find index | |
var idx = $('.ratio_elm').index($(this)); | |
var ratio = parseFloat(this.value); | |
var offset = parseFloat($('.offset_elm').eq(idx).val()); | |
dt_arr[idx] = (ratio * freq + offset) / context.sampleRate; | |
}); | |
//------------ | |
// Offset[Hz] | |
//------------ | |
$('.offset_elm') | |
.attr({max:9999, min:-999, step:0.01, value:0}) | |
.css({width:'58px'}) | |
.change(function(){ | |
// find index | |
var idx = $('.offset_elm').index($(this)); | |
var ratio = parseFloat($('.ratio_elm').eq(idx).val()); | |
var offset = parseFloat(this.value); | |
dt_arr[idx] = (ratio * freq + offset) / context.sampleRate; | |
}); | |
//-------- | |
// Volume | |
//-------- | |
var vol_arr = new Array(6); | |
$('.volume_elm') | |
.attr({max:100, min:0, value:0}) | |
.css({width:'58px'}) | |
.change(function(){ | |
// find index | |
var idx = $('.volume_elm').index($(this)); | |
var vol = parseFloat(this.value) | |
vol_arr[idx] = vol*vol/10000.0; | |
}); | |
//---------- | |
// Keyboard | |
//---------- | |
var ope1_arr = new Array(6); | |
var ope2_arr = new Array(6); | |
var t_arr = new Array(6); | |
var note = -1; | |
var freq_arr = $.map([-45, -43, -41, -40, -38, -36, -34, | |
-33, -31, -29, -28, -26, -24, -22, | |
-21, -19, -17, -16, -14, -12, -10, | |
-9, -7, -5, -4, -2, 0, 2, | |
3, 5, 7, 8, 10, 12, 14], | |
function(n){return 440.0*Math.pow(2.0,n/12.0);}); | |
$('.key_elem') | |
.css({width:'52px'}) | |
.mousedown(function(){ | |
var new_note = $('.key_elem').index($(this)); | |
if(note == new_note){ | |
// note off | |
note = -1; | |
$(this).css({color:'black'}); | |
} | |
else{ | |
if(note >= 0){ | |
// note off (if playing) | |
$('.key_elem').eq(note).css({color:'black'}); | |
} | |
freq = freq_arr[new_note]; | |
for(var j = 0; j < t_arr.length; j++){ | |
// reset | |
t_arr[j] = ope1_arr[j] = ope2_arr[j] = 0.0; | |
// update delta t | |
var ratio = parseFloat($('.ratio_elm').eq(j).val()); | |
var offset = parseFloat($('.offset_elm').eq(j).val()); | |
dt_arr[j] = (ratio * freq + offset) / context.sampleRate; | |
} | |
// note on | |
note = new_note; | |
$(this).css({color:'red'}); | |
} | |
}); | |
//------------------- | |
// set initial value | |
//------------------- | |
$('.matrix_elm').eq(6*0+0).val(0); | |
$('.matrix_elm').eq(6*0+1).val(20); | |
$('.matrix_elm').eq(6*0+2).val(0); | |
$('.matrix_elm').eq(6*0+3).val(0); | |
$('.matrix_elm').eq(6*0+4).val(0); | |
$('.matrix_elm').eq(6*0+5).val(0); | |
$('.matrix_elm').eq(6*1+0).val(0); | |
$('.matrix_elm').eq(6*1+1).val(0); | |
$('.matrix_elm').eq(6*1+2).val(0); | |
$('.matrix_elm').eq(6*1+3).val(0); | |
$('.matrix_elm').eq(6*1+4).val(16); | |
$('.matrix_elm').eq(6*1+5).val(0); | |
$('.matrix_elm').eq(6*2+0).val(0); | |
$('.matrix_elm').eq(6*2+1).val(0); | |
$('.matrix_elm').eq(6*2+2).val(33); | |
$('.matrix_elm').eq(6*2+3).val(44); | |
$('.matrix_elm').eq(6*2+4).val(0); | |
$('.matrix_elm').eq(6*2+5).val(0); | |
$('.matrix_elm').eq(6*3+0).val(0); | |
$('.matrix_elm').eq(6*3+1).val(61); | |
$('.matrix_elm').eq(6*3+2).val(0); | |
$('.matrix_elm').eq(6*3+3).val(0); | |
$('.matrix_elm').eq(6*3+4).val(0); | |
$('.matrix_elm').eq(6*3+5).val(0); | |
$('.matrix_elm').eq(6*4+0).val(38); | |
$('.matrix_elm').eq(6*4+1).val(0); | |
$('.matrix_elm').eq(6*4+2).val(0); | |
$('.matrix_elm').eq(6*4+3).val(0); | |
$('.matrix_elm').eq(6*4+4).val(0); | |
$('.matrix_elm').eq(6*4+5).val(0); | |
$('.matrix_elm').eq(6*5+0).val(0); | |
$('.matrix_elm').eq(6*5+1).val(0); | |
$('.matrix_elm').eq(6*5+2).val(0); | |
$('.matrix_elm').eq(6*5+3).val(0); | |
$('.matrix_elm').eq(6*5+4).val(64); | |
$('.matrix_elm').eq(6*5+5).val(28); | |
$('.ratio_elm').eq(0).val(2.0000); | |
$('.ratio_elm').eq(1).val(0.9995); | |
$('.ratio_elm').eq(2).val(1.0005); | |
$('.ratio_elm').eq(3).val(0.9995); | |
$('.ratio_elm').eq(4).val(1.0005); | |
$('.ratio_elm').eq(5).val(11.0000); | |
$('.offset_elm').eq(0).val(200.00); | |
$('.offset_elm').eq(1).val(2.00); | |
$('.offset_elm').eq(2).val(-2.00); | |
$('.offset_elm').eq(3).val(-2.00); | |
$('.offset_elm').eq(4).val(2.00); | |
$('.offset_elm').eq(5).val(0.00); | |
$('.volume_elm').eq(0).val(0); | |
$('.volume_elm').eq(1).val(0); | |
$('.volume_elm').eq(2).val(85); | |
$('.volume_elm').eq(3).val(0); | |
$('.volume_elm').eq(4).val(0); | |
$('.volume_elm').eq(5).val(50); | |
$('.matrix_elm').change(); // update | |
$('.ratio_elm').change(); // update | |
$('.offset_elm').change(); // update | |
$('.volume_elm').change(); // update | |
//--------------- | |
// audio process | |
//--------------- | |
var fmSynthProcess = function(evt){ | |
var left = evt.outputBuffer.getChannelData(0); | |
var right = evt.outputBuffer.getChannelData(1); | |
if(note >= 0){ | |
var PIx2= 2.0*Math.PI; | |
for(var i = 0; i < left.length; i++){ | |
// update operator | |
ope2_arr[0] = Math.sin(PIx2*( | |
+ t_arr[0] | |
+ fm_arr[ 0]*ope1_arr[0] | |
+ fm_arr[ 1]*ope1_arr[1] | |
+ fm_arr[ 2]*ope1_arr[2] | |
+ fm_arr[ 3]*ope1_arr[3] | |
+ fm_arr[ 4]*ope1_arr[4] | |
+ fm_arr[ 5]*ope1_arr[5])); | |
ope2_arr[1] = Math.sin(PIx2*( | |
+ t_arr[1] | |
+ fm_arr[ 6]*ope1_arr[0] | |
+ fm_arr[ 7]*ope1_arr[1] | |
+ fm_arr[ 8]*ope1_arr[2] | |
+ fm_arr[ 9]*ope1_arr[3] | |
+ fm_arr[10]*ope1_arr[4] | |
+ fm_arr[11]*ope1_arr[5])); | |
ope2_arr[2] = Math.sin(PIx2*( | |
+ t_arr[2] | |
+ fm_arr[12]*ope1_arr[0] | |
+ fm_arr[13]*ope1_arr[1] | |
+ fm_arr[14]*ope1_arr[2] | |
+ fm_arr[15]*ope1_arr[3] | |
+ fm_arr[16]*ope1_arr[4] | |
+ fm_arr[17]*ope1_arr[5])); | |
ope2_arr[3] = Math.sin(PIx2*( | |
+ t_arr[3] | |
+ fm_arr[18]*ope1_arr[0] | |
+ fm_arr[19]*ope1_arr[1] | |
+ fm_arr[20]*ope1_arr[2] | |
+ fm_arr[21]*ope1_arr[3] | |
+ fm_arr[22]*ope1_arr[4] | |
+ fm_arr[23]*ope1_arr[5])); | |
ope2_arr[4] = Math.sin(PIx2*( | |
+ t_arr[4] | |
+ fm_arr[24]*ope1_arr[0] | |
+ fm_arr[25]*ope1_arr[1] | |
+ fm_arr[26]*ope1_arr[2] | |
+ fm_arr[27]*ope1_arr[3] | |
+ fm_arr[28]*ope1_arr[4] | |
+ fm_arr[29]*ope1_arr[5])); | |
ope2_arr[5] = Math.sin(PIx2*( | |
+ t_arr[5] | |
+ fm_arr[30]*ope1_arr[0] | |
+ fm_arr[31]*ope1_arr[1] | |
+ fm_arr[32]*ope1_arr[2] | |
+ fm_arr[33]*ope1_arr[3] | |
+ fm_arr[34]*ope1_arr[4] | |
+ fm_arr[35]*ope1_arr[5])); | |
// output | |
left[i] = right[i] = vol_arr[0]*ope2_arr[0] | |
+ vol_arr[1]*ope2_arr[1] | |
+ vol_arr[2]*ope2_arr[2] | |
+ vol_arr[3]*ope2_arr[3] | |
+ vol_arr[4]*ope2_arr[4] | |
+ vol_arr[5]*ope2_arr[5]; | |
// update time | |
t_arr[0] += dt_arr[0]; | |
t_arr[1] += dt_arr[1]; | |
t_arr[2] += dt_arr[2]; | |
t_arr[3] += dt_arr[3]; | |
t_arr[4] += dt_arr[4]; | |
t_arr[5] += dt_arr[5]; | |
if(t_arr[0] >= 1.0) t_arr[0] -= 1.0; | |
else if(t_arr[0] < 0.0) t_arr[0] += 1.0; | |
if(t_arr[1] >= 1.0) t_arr[1] -= 1.0; | |
else if(t_arr[1] < 0.0) t_arr[1] += 1.0; | |
if(t_arr[2] >= 1.0) t_arr[2] -= 1.0; | |
else if(t_arr[2] < 0.0) t_arr[2] += 1.0; | |
if(t_arr[3] >= 1.0) t_arr[3] -= 1.0; | |
else if(t_arr[3] < 0.0) t_arr[3] += 1.0; | |
if(t_arr[4] >= 1.0) t_arr[4] -= 1.0; | |
else if(t_arr[4] < 0.0) t_arr[4] += 1.0; | |
if(t_arr[5] >= 1.0) t_arr[5] -= 1.0; | |
else if(t_arr[5] < 0.0) t_arr[5] += 1.0; | |
// swap operator | |
var ope_swap = ope2_arr; | |
ope2_arr = ope1_arr; | |
ope1_arr = ope_swap; | |
} | |
} | |
else{ | |
// silent | |
for(var i = 0; i < left.length; i++){ | |
left[i] = right[i] = 0.0; | |
} | |
} | |
}; | |
source = context.createScriptProcessor(1024, 0, 2); | |
source.onaudioprocess = fmSynthProcess; | |
source.connect(context.destination); | |
}); | |
</script> | |
</head> | |
<body> | |
FM synthesis test using Web Audio API (Google Chrome only) | |
<hr/> | |
FM Matrix | |
<br/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<br/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<br/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<br/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<br/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<br/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<input type='number' class='matrix_elm'/> | |
<p/> | |
Ratio | |
<br/> | |
<input type='number' class='ratio_elm'/> | |
<input type='number' class='ratio_elm'/> | |
<input type='number' class='ratio_elm'/> | |
<input type='number' class='ratio_elm'/> | |
<input type='number' class='ratio_elm'/> | |
<input type='number' class='ratio_elm'/> | |
<p/> | |
Offset[Hz] | |
<br/> | |
<input type='number' class='offset_elm'/> | |
<input type='number' class='offset_elm'/> | |
<input type='number' class='offset_elm'/> | |
<input type='number' class='offset_elm'/> | |
<input type='number' class='offset_elm'/> | |
<input type='number' class='offset_elm'/> | |
<p/> | |
Volume | |
<br/> | |
<input type='number' class='volume_elm'/> | |
<input type='number' class='volume_elm'/> | |
<input type='number' class='volume_elm'/> | |
<input type='number' class='volume_elm'/> | |
<input type='number' class='volume_elm'/> | |
<input type='number' class='volume_elm'/> | |
<p/> | |
Keyboard | |
<br/> | |
<input type='button' class='key_elem' value='C0'/> | |
<input type='button' class='key_elem' value='D0'/> | |
<input type='button' class='key_elem' value='E0'/> | |
<input type='button' class='key_elem' value='F0'/> | |
<input type='button' class='key_elem' value='G0'/> | |
<input type='button' class='key_elem' value='A1'/> | |
<input type='button' class='key_elem' value='B1'/> | |
<br/> | |
<input type='button' class='key_elem' value='C1'/> | |
<input type='button' class='key_elem' value='D1'/> | |
<input type='button' class='key_elem' value='E1'/> | |
<input type='button' class='key_elem' value='F1'/> | |
<input type='button' class='key_elem' value='G1'/> | |
<input type='button' class='key_elem' value='A2'/> | |
<input type='button' class='key_elem' value='B2'/> | |
<br/> | |
<input type='button' class='key_elem' value='C2'/> | |
<input type='button' class='key_elem' value='D2'/> | |
<input type='button' class='key_elem' value='E2'/> | |
<input type='button' class='key_elem' value='F2'/> | |
<input type='button' class='key_elem' value='G2'/> | |
<input type='button' class='key_elem' value='A3'/> | |
<input type='button' class='key_elem' value='B3'/> | |
<br/> | |
<input type='button' class='key_elem' value='C3'/> | |
<input type='button' class='key_elem' value='D3'/> | |
<input type='button' class='key_elem' value='E3'/> | |
<input type='button' class='key_elem' value='F3'/> | |
<input type='button' class='key_elem' value='G3'/> | |
<input type='button' class='key_elem' value='A4'/> | |
<input type='button' class='key_elem' value='B4'/> | |
<br/> | |
<input type='button' class='key_elem' value='C4'/> | |
<input type='button' class='key_elem' value='D4'/> | |
<input type='button' class='key_elem' value='E4'/> | |
<input type='button' class='key_elem' value='F4'/> | |
<input type='button' class='key_elem' value='G4'/> | |
<input type='button' class='key_elem' value='A5'/> | |
<input type='button' class='key_elem' value='B5'/> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment