Skip to content

Instantly share code, notes, and snippets.

@fukuroder
Last active December 28, 2015 12:29
Show Gist options
  • Save fukuroder/7501079 to your computer and use it in GitHub Desktop.
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
<!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