tone.js とか使った方がいろいろできます。
- MIDIっぽく鳴らせる
- 単音専用なのであまり考えずにシンプルな用途には使いやすい
posnd2.mjs .js
// posnd.mjs
var loglevel = 0;
function infoLog(str){
if(loglevel > 1){
console.log("info: "+str);
}
}
function errLog(str){
if(loglevel > 0){
console.log("error: "+str);
}
}
class posnd2{
constructor(){
this.audioctx = null;
}
init (opt) {
if(opt != null){
if(typeof opt.log === 'number'){
loglevel = opt.log;
}
}
infoLog("posnd:init()");
window.AudioContext = window.AudioContext||window.webkitAudioContext;
this.isPlaying = false;
this.freq = 220;
this.audioctx = new AudioContext();
this.osc = this.audioctx.createOscillator();
this.osc.frequency.value = this.freq;
this.osc.type = "sine";
this.gain = this.audioctx.createGain();
this.gain.gain.value = 0;
this.osc.connect(this.gain);
this.osc.start();
this.gain.connect(this.audioctx.destination);
this.adsr = [0,0,1.0,0];
this.svol = 0;
this.glide = 0;
}
noteOn(note,velocity){
infoLog("posnd:noteOn("+note+","+velocity+")"+this.adsr);
var volume = velocity / 127;
this.freq = NOTE_FREQ[note];
var ctime = this.audioctx.currentTime;
this.osc.frequency.linearRampToValueAtTime(this.freq,ctime + this.glide);
this.gain.gain.linearRampToValueAtTime(volume, ctime + this.adsr[0]);
this.svol = this.adsr[2]*volume;
this.gain.gain.setTargetAtTime(this.svol,ctime + this.adsr[0], this.adsr[1]);
this.isPlaying = true;
}
play(value,velocity){
infoLog("posnd:start("+value+","+velocity+")"+this.adsr);
var volume = velocity / 127;
this.freq = this.valueToFreq(value);
var ctime = this.audioctx.currentTime;
this.osc.frequency.linearRampToValueAtTime(this.freq,ctime + this.glide);
this.gain.gain.linearRampToValueAtTime(volume, ctime + this.adsr[0]);
this.svol = this.adsr[2]*volume;
this.gain.gain.setTargetAtTime(this.svol,ctime + this.adsr[0], this.adsr[1]);
}
valueToFreq(value) {
// const A4 = 800;
// const A4_FREQUENCY = 55;
const A4 = 276;
const A4_FREQUENCY = 440;
return A4_FREQUENCY * Math.pow(2, (value - A4) / 48);
}
stop(){
this.noteOff();
}
noteOff(){
infoLog("posnd:noteOff()");
var ctime = this.audioctx.currentTime;
this.gain.gain.linearRampToValueAtTime(0, ctime + this.adsr[3]);
}
note(note,velocity,duration){
return new Promise((resolve)=>{
infoLog("posnd:note("+note+","+velocity+","+duration+")");
this.noteOn(note,velocity);
setTimeout(()=>{
this.noteOff();
resolve();
},duration);
});
}
setADSR(adsr){
infoLog("PoorSound setADSR("+adsr+")");
if(adsr.length == 4){
this.adsr = adsr;
}else{
errLog("setADSR() parameter error. Please set [a,d,s,r] ");
}
}
setGlide(glide){
infoLog("PoorSound setGlide("+glide+")");
this.glide = glide;
}
setOsc(type){
infoLog("PoorSound setOSCType("+type+")");
switch(type){
case 0:
case "sine":
case "sin":
this.osc.type = "sine";
break;
case 1:
case "square":
case "sqr":
this.osc.type = "square";
break;
case 2:
case "sawtooth":
case "saw":
this.osc.type = "sawtooth";
break;
case 3:
case "triangle":
case "tri":
this.osc.type = "triangle";
break;
default:
errLog("setOSCType() parameter error");
break;
}
}
resume(){
if(this.audioctx != null){
this.audioctx.resume();
}
}
wait(ms){
return new Promise((resolve)=>{setTimeout(resolve,ms);});
}
}
const NOTE_FREQ = [
8.1757989156,8.6619572180,9.1770239974,9.7227182413,10.3008611535,10.9133822323,11.5623257097,12.2498573744,
12.9782717994,13.7500000000,14.5676175474,15.4338531643,16.3515978313,17.3239144361,18.3540479948,19.4454364826,
20.6017223071,21.8267644646,23.1246514195,24.4997147489,25.9565435987,27.5000000000,29.1352350949,30.8677063285,
32.7031956626,34.6478288721,36.7080959897,38.8908729653,41.2034446141,43.6535289291,46.2493028390,48.9994294977,
51.9130871975,55.0000000000,58.2704701898,61.7354126570,65.4063913251,69.2956577442,73.4161919794,77.7817459305,
82.4068892282,87.3070578583,92.4986056779,97.9988589954,103.8261743950,110.0000000000,116.5409403795,123.4708253140,
130.8127826503,138.5913154884,146.8323839587,155.5634918610,164.8137784564,174.6141157165,184.9972113558,195.9977179909,
207.6523487900,220.0000000000,233.0818807590,246.9416506281,261.6255653006,277.1826309769,293.6647679174,311.1269837221,
329.6275569129,349.2282314330,369.9944227116,391.9954359817,415.3046975799,440.0000000000,466.1637615181,493.8833012561,
523.2511306012,554.3652619537,587.3295358348,622.2539674442,659.2551138257,698.4564628660,739.9888454233,783.9908719635,
830.6093951599,880.0000000000,932.3275230362,987.7666025122,1046.5022612024,1108.7305239075,1174.6590716696,1244.5079348883,
1318.5102276515,1396.9129257320,1479.9776908465,1567.9817439270,1661.2187903198,1760.0000000000,1864.6550460724,1975.5332050245,
2093.0045224048,2217.4610478150,2349.3181433393,2489.0158697766,2637.0204553030,2793.8258514640,2959.9553816931,3135.9634878540,
3322.4375806396,3520.0000000000,3729.3100921447,3951.0664100490,4186.0090448096,4434.9220956300,4698.6362866785,4978.0317395533,
5274.0409106059,5587.6517029281,5919.9107633862,6271.9269757080,6644.8751612791,7040.0000000000,7458.6201842894,7902.1328200980,
8372.0180896192,8869.8441912599,9397.2725733570,9956.0634791066,10548.0818212118,11175.3034058561,11839.8215267723,12543.8539514160
];
export default posnd2;
使い方
インポート
.js
import posnd from "./posnd2.mjs";
初期化
.js
const syn = new posnd();
syn.init();
設定(Glide + ADSR)
.js
syn.setGlide(0.5);
syn.setADSR([0.1,1.0,1.0,4]);
resume()
1度ユーザー操作コンテキストで呼んでください。
.js
syn.resume();
play()
音を鳴らします。
.js
syn.play(note,velocity);
noteはMIDI Note Numberと一緒です。(1-127)
velocityは音量です。(速さではない!)(1-127)
stop()
音を止めます。
.js
syn.stop();
ドレミファソラシド
.js
const melody = [48,50,52,53,55,57,59,60];
for(let cnt=0;cnt<melody.length;cnt ++){
syn.play(melody[cnt],100);
await syn.wait(100);
syn.stop();
}
まとめ
まーなんて私専用なんでしょ!