JavaScriptã§ãªã¢ã«ã¿ã¤ã ã«é³ãåºãã¨ãã«ç°¡å便å©ãªãã¤ã¤ãã£ã
å
æ¥ä½æããã関西電気保安協会リズムマシーンãã¨ãONE-LINER-ORCHESTRAãã
ã§ä½¿ããããã«ãªãã¾ããã
ã©ã¡ãã pico-player.js ã£ã¦ããæä½ã©ã¤ãã©ãªã使ã£ã¦ãããã ãã©ãæ®éã«ä¾¿å©ãªã®ã§ç´¹ä»ãã¾ãã
https://gist.github.com/1342081
CoffeeScriptã§æ¸ãã¦ã³ã³ãã¤ã«ãã¦ãã¾ããããJavaScriptæ¸ããªããªã£ã¦ãã¾ããã
2011/11/08追è¨
å¥ã¿ãé¸ææã«é³ãéåããæ¸å°ã解æ¶ãã¾ããã
é¢é£è¨äºï¼ http://d.hatena.ne.jp/mohayonao/20111108/1320756534
2011/11/07 追è¨
CoffeeScriptã§æ¸ããåã¯ç¡é§ãå¤ãã®ã§ãJavaScriptã§æ¸ãç´ãããã¼ã¸ã§ã³æ´æ°ãã¾ããã
å
·ä½çã«ã©ããªãã¨ããã¦ããã®ãã¯ä»¥åã«æ¸ããè¨äºãåèã«ãªãããç¥ãã¾ããã
JavaScriptでリアルタイムに音を鳴らす方法を3つほど - つまみ食う
使ãæ¹
ããã ããé常ã«ç°¡åã
// ãã©ã¦ã¶ã«å¿ãããã¬ã¤ã¤ã¼ãªãã¸ã§ã¯ããçæ var player = pico.getplayer(options); // å¼æ°ã¯ãªãã¸ã§ã¯ãã§æ¸¡ãã以ä¸ã¯ããã©ã«ãå¤ã options = { samplerate: 44100, // ãµã³ããªã³ã°ã¬ã¼ã channel: 1, // ãã£ã³ãã«æ° duration: 40, // ã¹ããªã¼ã ã®åå²æé(ããªç§) slice: 8 // ã¹ããªã¼ã ã»ã«ã®åå²æ° }; // ã¸ã§ãã¬ã¼ã¿(å¾è¿°)ãå¼æ°ã« play player.play(generator); // åçä¸ï¼ player.isPlaying(); // é³ãæ¢ãã player.stop(); // ãã¬ã¤ã¤ã¼ã®ç¨®é¡ ["WebKitPlayer", "MozPlayer", "HTML5AudioPlayer"] console.log( player.gettype() );
playerã¯ä»¥ä¸ã®ä¸å¤ããããã£ãæã¤
player.SAMPLERATE // ãµã³ããªã³ã°ã¬ã¼ã player.CHANNEL // ãã£ã³ãã«æ° player.STREAM_FULL_SIZE // ã¹ããªã¼ã ã®ãµã¤ãº player.STREAM_CELL_SIZE // ã¹ããªã¼ã ã»ã«ã®ãµã¤ãº player.STREAM_CELL_COUNT // ã¹ããªã¼ã ã»ã«ã®åå²æ° player.PLAY_INTERVAL // ã¹ããªã¼ã ã®åå²æé(ããªç§)
ãã®ä¸ã§æ°ã«ããªãã¨ãããªãã®ã¯ããµã³ããªã³ã°ã¬ã¼ãã¨ã¹ããªã¼ã ã®ãµã¤ãºãããã§ãã¨ã¯ãã¾ãã
ãã¨ããªãã¸ã§ã¯ãçææã®å¼æ°ã¨å®éã«çæããããªãã¸ã§ã¯ãã®ããããã£ã¯ç°ãªãã®ã§æ³¨æãå¿
è¦ããã¨ãã°ãChrome(Web Audio API)ã¯ãµã³ããªã³ã°ã¬ã¼ãåºå®ãªã®ã§ãèµ·åæã®å¼æ°ã¯ç¡è¦ãããããOpera(HTMLAudioElementã大éçæ)ã¯ä»ã®2ã¤ã«æ¯ã¹ã¦ç¡çããå¦çãã¦ããé¢ä¿ã§ã¹ããªã¼ã ã®åå²æéãé·ã(400ããªç§ä»¥ä¸)ã«è¨å®ãããããã®ã¸ãã¯ååã«æ¤è¨¼ã§ããªãã®ã§èª¿æ´å¿
è¦ããã
ã¸ã§ãã¬ã¼ã¿
- ã¸ã§ãã¬ã¼ã¿ã¯å®éã«é³ã®ã·ã°ãã«ãåºåããããã®ãªãã¸ã§ã¯ã
- player.play(generator) ãå®è¡ããã¨ãgenerator.next ãå®æçã«å¼ã°ãã
- generator.next 㯠player.STREAM_FULL_SIZE ã®å¤§ããã® Float32Array (-1.0 <= signal <= +1.0)ãè¿ã
- ãã£ã³ãã«æ°ã 2 ã®ã¨ã㯠[L, R, L, R, .. ] ã¨ããé åãè¿ãã°ãã
以ä¸ã¯ã¦ã§ã¼ããã¼ãã«æ¹å¼ã®ã¸ã§ãã¬ã¼ã¿ã®ãµã³ãã«ã
ãããããé³ã®æ³¢å½¢ã®é
åãç¨æãã¦ããã¦ãé
åã®è¦ç´ ãé£ã°ã大ããã§é³ã®é«ããã³ã³ããã¼ã«ãã¦ãã
var TABLE_SIZE = 1024; var sinetable = new Float32Array(TABLE_SIZE); for (var i = 0; i < TABLE_SIZE; i++) { sinetable[i] = Math.sin(2 * Math.PI * (i / TABLE_SIZE)); } var ToneGenerator = function(player, wavelet, frequency) { this.wavelet = wavelet; this.frequency = frequency; this.phase = 0; this.phaseStep = TABLE_SIZE * frequency / player.SAMPLERATE; this.stream_full_size = player.STREAM_FULL_SIZE; }; ToneGenerator.prototype.next = function() { var wavlet = this.wavelet; var phase = this.phase; var phaseStep = this.phaseStep; var table_size = TABLE_SIZE; var stream = new Float32Array(this.stream_full_size); for (var i = 0, imax = this.stream_full_size; i < imax; i++) { stream[i] = wavelet[(phase|0) % table_size]; phase += phaseStep; } this.phase = phase; return stream; }; // 440Hzã®ãµã¤ã³æ³¢ãåçã㦠2ç§å¾ã«æ¢ã¾ã var player = pico.getplayer(); var gen = new ToneGenerator(player, sinetable, 440); player.play(gen); setTimeout(function() { player.stop(); }, 2000);
ONE-LINER-ORCHESTRAã§ã¯ä¸è¨ã®ã¸ã§ãã¬ã¼ã¿ã¿ãããªãã¤ãè¤æ°ç®¡çãã¦å ç®åæãã¦è¿ãã¸ã§ãã¬ã¼ã¿ã player ã«ä¸ãã¦è¤æ°ã®é³ãåºãã¦ããã大量の音を合成している例
ä¸è¨ã®ãµã³ãã«ã¯ããã§ç¢ºèªã§ããã
pico-player.js / pico-player.coffee
ã¡ãã£ã¨ä¾¿å©
ãªãã·ã§ã³å¼æ°ã« slice ãä¸ãã㨠STREAM_CELL_SIZE ã¨ããã®ãè¨ç®ãããã
STREAM_CELL_SIZE 㯠STREAM_FULL_SIZE/slice ã§è¨ç®ãããå¤ã§ãplayer èªä½ã®åä½ã«ã¯ä½ã®å½±é¿ããªããã ãã©ãããã¨ä¾¿å©ãªã®ã§å
¥ãã¦ããã
ãã¨ãã°ã¢ããã©ããã¯ãªã¢ã«ãã¸ã¨ã¼ã¿ãä½ã£ã¦ããã¨ããé³ç¨ã®åãæ¿ãããµã³ãã«æ°ã¯
sample = samplecount = (60/BPM) * SAMPLERATE * (4/LENGTH)
ã§è¨ç®ã§ããã®ã§ãsamplecount-- ãã¦ãã£ã¦ 0 ã«ãªã£ãããé³ç¨ãåãæ¿ããã°è¯ããã ãã©ã1ãµã³ãã«ãã¨ã« if ã§æ¡ä»¶å¤å®ããã®ã¯éå¹çã§ãããç¨åº¦å¤§éæ(è³ã§ã¯å¤å¥ã§ããªã)ã«å¦çããã»ããè¨ç®éãæ¸ã£ã¦è¯ããé³éã¨ãä½ä½ã¨ããåãããã®å¤§éæãªæ°åã«ä½¿ããã®ã STREAM_CELL_SIZEã
var samplerate = player.SAMPLERATE; var stream_cell_size = player.STREAM_CELL_SIZE; var stream = new Float32Array(player.STREAM_FULL_SIZE); var k = 0; for (var i = 0, imax = player.STREAM_CELL_COUNT; i < imax; i++) { samplecount -= STREAM_CELL_SIZE; if (samplecount <= 0) { // é³ç¨ãåãæ¿ããããã®å¦ç // phaseStep = TABLE_SIZE * new_frequency / samplerate; // ã¿ãããªã® samplecount += sample; } for (var j = 0; j < stream_cell_size; j++) { stream[k++] = wavelet[(phase|0)&TABLE_SIZE]; phase += phaseStep; } } return stream;
ãã©ã¦ã¶æ¦äºã®çµæãJavaScript ããããé«éã«ãªã£ãã¨ãã£ã¦ãDSPå¦çã¯çµæ§éãã®ã§è¨ç®ãçç¥ããã®ãéè¦ã§ãæåã®ä¾ã®ãµã¤ã³æ³¢ãåçæã«ã¯ Math.sin ã¿ãããªè¤éãªè¨ç®ã¯ãªã¢ã«ã¿ã¤ã ã«è¡ããªããããªå·¥å¤«ãããããSTREAM_CELL_SIZEã使ã£ãä¾ã¿ããã«å¦çåä½ãæ¸ããããå°éãªåªåãå¿
è¦ã£ã½ãã
ã¡ã¢
ã»Operaã§ã¯ Float32Array ã使ããªãã£ã½ã (pico-player.jså
ã§å½Float32Arrayãã§ã£ã¡ä¸ãã¦ãã)
ã»Operaã¨åãããæ¹ã¯Safariã§ã¯åºã¾ãããã¾ã
ã»iOS, Androidããã¡ã
ã»ä¸è¨ã®ä¾ã§ phaseStep ã¯æ¯åè¨ç®ãã¦ãããã©ã MIDIãã¼ãçªå·ã®ç´°ããçã¿ãããªã®ãç¨æããã¨å¹çããã
# åé³ãresolutionåã«åå²ããé³çªå·->phseStepã®ãã¼ãã«ãã¤ãã def calc_steps(size=8192, resolution=32): center = size >> 1 def calcStep(i): freq = 440.0 * ((2.0**(1.0/(12*resolution)))**(i-center)) return TABLE_LENGTH * freq / SAMPLERATE return tuple(calcStep(i) for i in xrange(size))
*1:nightly buildsã§