ActionScript日記 - サウンドのリアルタイム生成(4)
前回のエントリが割と好評だったので、調子にのって続きを。
予告どおり popforge に対応して、対応コマンドも少し増やしてみました。これで少しは曲のバリエーションが増えるかな?
次は HTML から Flash に MML を渡す部分を作るか、それともLPFでも付けようかなぁ。繰り返し記号にも対応しないとマズイですよね。既に処理が重いのはどうしよう。
まあとりあえず、新しく対応したコマンドとソースファイルが見たい方は続きをどうぞ。
このMMLは大文字・小文字の区別をしません。
CDEFGAB | 音名。ドレミファソラシに対応。 |
R | 休符。 |
T[n] | テンポ指定。BPM。 |
O[n] | オクターブ指定。0~8まで。 |
L[n] | デフォルト音長指定。 |
V[n] | ベロシティ(音量)指定。0~15まで。 |
< | オクターブを上げます。 |
> | オクターブを下げます。 |
& | タイまたはスラー。C2&C8と書けばタイ、C&D&Eと書けばスラー。 |
;(セミコロン) | 次のトラックへ移ります。 |
@[n] | 音色を選びます。0:サイン波/1:ノコギリ波/2:三角波/3:パルス波/4:ホワイトノイズ。 |
@E1,[n],[n],[n],[n] | エンベロープの設定。attack、decay、sustain、releaseの順で、それぞれ0~127まで。 |
Q[n] | ゲートタイムの割合の指定。ある音を再生するとき、実際には指定された音長のn/16だけが発音され、残りの時間は休符になります。 |
ソースは以下のとおり。ちょっとブログに貼り付ける量じゃなかったかもしれないですね…。
flmml.mxml
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:flmml="com.txt_nifty.sketch.flmml.*" layout="absolute"> <mx:Panel title="FlMML" width="100%" height="100%" paddingTop="10" paddingBottom="10" paddingLeft="10" paddingRight="10"> <mx:TextArea id="input" width="100%" height="100%" fontSize="14"> <mx:text> t80l16 o5rcde fdec g8<c8 c48>b48<c48>b<c8 d>gab <c>abg <d8g8 g48f48g48fg8 eagf egfa gfed cedf; t80l16 o4r2 rcde fdec g8>g8< r4 rgab<c>abg l8 <c>b<cde>gab </mx:text> </mx:TextArea> <mx:Button id="playButton" label="Play" click="player.play(input.text);"/> <flmml:FlMML id="player" /> </mx:Panel> </mx:Application>FlMML.as
package com.txt_nifty.sketch.flmml { import mx.core.UIComponent; public class FlMML extends UIComponent { private var m_mml:MML; public function FlMML() { m_mml = new MML(); } public function play(mml:String):void { m_mml.play(mml); } } }MChannel.as
package com.txt_nifty.sketch.flmml { import de.popforge.audio.output.*; public class MChannel { private var m_envelope:MEnvelope; private var m_oscillator:MOscillator; private var m_velocity:Number; // velocity (max:127) public function MChannel() { m_envelope = new MEnvelope(0.0, 0.4, 0.2, 0); m_oscillator = new MOscillator(); m_velocity = 100; } public function noteOn(frequency:Number, velocity:int):void { m_oscillator.setPitch(frequency); m_envelope.triggerEnvelope(); m_velocity = velocity; } public function noteOff():void { m_envelope.releaseEnvelope(); } public function setFrequency(frequency:Number):void { m_oscillator.setPitch(frequency); } public function setForm(form:int):void { m_oscillator.setForm(form); } public function setEnvelopeAD(attack:int, decay:int):void { m_envelope.setAD(attack * (1.0 / 127.0), decay * (1.0 / 127.0)); } public function setEnvelopeSR(sustain:int, release:int):void { m_envelope.setSR(sustain * (1.0 / 127.0), release * (1.0 / 127.0)); } public function getNextSample():Number { return m_oscillator.getNextSample() * m_envelope.getNextAmplitude() * m_velocity * (1.0 / 127.0); } } }MEnvelope.as
package com.txt_nifty.sketch.flmml { /** * This class was created based on "ADSREnvelope.as". The original code is in "nu.mine.flashnet.sound.synthesis". */ import de.popforge.audio.output.*; public class MEnvelope { public var m_attack:Number; public var m_decay:Number; public var m_sustain:Number; public var m_release:Number; private var m_currentVal:Number; private var m_releaseStep:Number; private var m_releasing:Boolean; private var m_playing:Boolean; private var m_timeInSamples:int; private var m_startAmplitude:Number; public function MEnvelope(attack:Number, decay:Number, sustain:Number, release:Number) { m_playing = false; m_attack = attack; m_decay = decay; m_sustain = sustain; m_release = release; m_currentVal = 0; m_releasing = true; m_releaseStep = 0; } public function setAD(attack:Number, decay:Number):void { m_attack = attack; m_decay = decay; } public function setSR(sustain:Number, release:Number):void { m_sustain = sustain; m_release = (release > 0) ? release : 1; } public function triggerEnvelope():void { m_playing = true; m_releasing = false; m_startAmplitude = m_currentVal; m_timeInSamples = 1; //no point in starting at zero as this gives a sample worth of zero before onset of env. } public function releaseEnvelope():void { m_releasing = true; var releaseTimeInSamples:int = (m_release * Audio.RATE44100); m_releaseStep = (m_currentVal / releaseTimeInSamples); } public function getNextAmplitude():Number { if (!m_playing) return 0; var val:Number; var offset:Number = (m_timeInSamples as Number) / Audio.RATE44100; if (!m_releasing) { if (offset < m_attack) { // attack phase val = m_startAmplitude + (1 - m_startAmplitude) * offset / m_attack; } else if (offset < (m_attack + m_decay)) { //decay phase val = 1.0 - ((offset - m_attack) / m_decay) * (1.0 - m_sustain); } else if (offset >= (m_attack + m_decay)) { //sustain phase val = m_sustain; } } else { val = m_currentVal - m_releaseStep; //release phase } if (val < 0) { m_playing = false; } m_currentVal = Math.max(0, val); ++m_timeInSamples; return m_currentVal; } } }MEvent.as
package com.txt_nifty.sketch.flmml { public class MEvent { private var m_delta:int; private var m_status:int; private var m_data0:int; private var m_data1:int; public function MEvent() { set(MStatus.NOP, 0, 0); } public function set(status:int, data0:int, data1:int):void { m_status = status; m_data0 = data0; m_data1 = data1; } public function setEOT():void { set(MStatus.EOT, 0, 0); } public function setNoteOn(noteNo:int, vel:int):void { set(MStatus.NOTE_ON, noteNo, vel); } public function setNoteOff(noteNo:int, vel:int):void { set(MStatus.NOTE_OFF, noteNo, vel); } public function setTempo(tempo:int):void { set(MStatus.TEMPO, tempo, 0); } public function setVolume(vol:int):void { set(MStatus.VOLUME, vol, 0); } public function setNote(noteNo:int):void { set(MStatus.NOTE, noteNo, 0); } public function setForm(form:int):void { set(MStatus.FORM, form, 0); } public function setEnvelopeAD(a:int, d:int):void { set(MStatus.ENVELOPE_AD, a, d); } public function setEnvelopeSR(s:int, r:int):void { set(MStatus.ENVELOPE_SR, s, r); } public function setDelta(delta:int):void { m_delta = delta; } public function getStatus():int { return m_status; } public function getDelta():int { return m_delta; } public function getNoteNo():int { return m_data0; } public function getVelocity():int { return m_data1; } public function getTempo():int { return m_data0; } public function getVolume():int { return m_data0; } public function getForm():int { return m_data0; } public function getEnvelopeA():int { return m_data0; } public function getEnvelopeD():int { return m_data1; } public function getEnvelopeS():int { return m_data0; } public function getEnvelopeR():int { return m_data1; } public static function getFrequency(noteNo:int):Number { return frequencyMap[noteNo]; } private static const frequencyMap:Array = new Array( // octave -2 8.18, // C 0 8.66, // C# 1 9.18, // D 2 9.73, // D# 3 10.30, // E 4 10.92, // F 5 11.56, // F# 6 12.25, // G 7 12.98, // G# 8 13.75, // A 9 14.57, // A# 10 15.44, // B 11 // octave -1 16.35, // C 12 17.32, // C# 13 18.35, // D 14 19.45, // D# 15 20.60, // E 16 21.83, // F 17 23.12, // F# 18 24.50, // G 19 25.96, // G# 20 27.50, // A 21 29.14, // A# 22 30.87, // B 23 // octave 0 32.70, // C 24 34.65, // C# 25 36.71, // D 26 38.89, // D# 27 41.20, // E 28 43.65, // F 29 46.25, // F# 30 49.00, // G 31 51.91, // G# 32 55.00, // A 33 58.27, // A# 34 61.74, // B 35 // octave 1 65.41, // C 36 69.30, // C# 37 73.42, // D 38 77.78, // D# 39 82.41, // E 40 87.41, // F 41 92.50, // F# 42 98.00, // G 43 103.83, // G# 44 110.00, // A 45 116.54, // A# 46 123.47, // B 47 // octave 2 130.81, // C 48 138.59, // C# 49 146.83, // D 50 155.56, // D# 51 164.81, // E 52 174.61, // F 53 185.00, // F# 54 196.00, // G 55 207.65, // G# 56 220.00, // A 57 233.08, // A# 58 246.94, // B 59 // octave 3 261.63, // C 60 277.18, // C# 61 293.66, // D 62 311.13, // D# 63 329.63, // E 64 349.23, // F 65 369.99, // F# 66 392.00, // G 67 415.30, // G# 68 440.00, // A 69 466.16, // A# 70 493.88, // B 71 // octave 4 523.25, // C 72 554.37, // C# 73 587.33, // D 74 622.25, // D# 75 659.26, // E 76 698.46, // F 77 739.99, // F# 78 783.99, // G 79 830.61, // G# 80 880.00, // A 81 932.33, // A# 82 987.77, // B 83 // octave 5 1046.50, // C 84 1108.73, // C# 85 1174.66, // D 86 1244.51, // D# 87 1318.51, // E 88 1396.91, // F 89 1479.98, // F# 90 1567.98, // G 91 1661.22, // G# 92 1760.00, // A 93 1864.66, // A# 94 1975.53, // B 95 // octave 6 2093.00, // C 96 2217.46, // C# 2349.32, // D 2489.02, // D# 2637.02, // E 2793.83, // F 2959.96, // F# 3135.96, // G 3322.44, // G# 3520.00, // A 3729.31, // A# 3951.07, // B // octave 7 4186.01, // C 108 4434.92, // C# 109 4698.64, // D 110 4678.03, // D# 111 5274.04, // E 112 5587.65, // F 113 5919.91, // F# 114 6271.93, // G 115 6644.88, // G# 116 7040.00, // A 117 7458.62, // A# 118 7902.13, // B 119 // octave 8 8372.02, // C 120 8869.84, // C# 121 9397.27, // D 122 9956.06, // D# 123 10548.08, // E 124 11175.30, // F 125 11839.82, // F# 126 12543.85, // G 127 13289.75, // G# 128 14080.00, // A 129 14917.24, // A# 130 15804.27 // B 131 ); } }MML.as
package com.txt_nifty.sketch.flmml { import de.popforge.audio.output.*; public class MML { private var m_sequencer:MSequencer; private var m_tracks:Array; private var m_string:String; private var m_trackNo:int; private var m_octave:int; private var m_velocity:int; // default velocity private var m_length:int; // default length private var m_tempo:int; private var m_letter:int; private var m_keyoff:int; private var m_gate:int; private var m_maxGate:int; private var m_form:int; public function MML() { m_sequencer = new MSequencer(); } private function len2tick(len:int):int { if (len == 0) len = m_length; return 384/len; } private function note(noteNo:int):void { //trace("note"+noteNo); noteNo += getKeySig(); var len:int; len = getUInt(m_length); var tick:int = len2tick(len); tick = getDot(tick); var keyon:int = (m_keyoff == 0) ? 0 : 1; m_keyoff = 1; if (getChar() == '&') { m_letter++; m_keyoff = 0; } m_tracks[m_trackNo].recNote(noteNo + m_octave*12, tick, m_velocity, keyon, m_keyoff); } private function rest():void { //trace("rest"); var len:int; len = getUInt(m_length); var tick:int = len2tick(len); tick = getDot(tick); m_tracks[m_trackNo].recRest(tick); } private function atmark():void { var c:String = getChar(); var o:int = 1, a:int = 0, d:int = 64, s:int = 32, r:int = 0; switch(c) { case 'e': m_letter++; o = getUInt(o); if (getChar() == ',') m_letter++; a = getUInt(a); if (getChar() == ',') m_letter++; d = getUInt(d); if (getChar() == ',') m_letter++; s = getUInt(s); if (getChar() == ',') m_letter++; r = getUInt(r); //trace("A"+a+",D"+d+",S"+s+",R"+r); m_tracks[m_trackNo].recEnvelope(a, d, s, r); break; default: m_form = getUInt(m_form); m_tracks[m_trackNo].recForm(m_form); break; } } private function first():void { var c:String = getCharNext(); var i:int; switch(c) { case "c": note(0); break; case "d": note(2); break; case "e": note(4); break; case "f": note(5); break; case "g": note(7); break; case "a": note(9); break; case "b": note(11); break; case "r": rest(); break; case "o": m_octave = getUInt(m_octave); if (m_octave < -2) m_octave = -2; if (m_octave > 8) m_octave = 8; break; case "v": m_velocity = getUInt(m_velocity) * 8 + 7; if (m_velocity > 127) m_velocity = 127; break; case "l": m_length = getUInt(m_length); if (m_length == 0) m_length = 4; break; case "t": m_tempo = getUInt(m_tempo); if (m_tempo == 0) m_tempo = 1; m_tracks[m_trackNo].recTempo(m_tempo); break; case "q": m_gate = getUInt(m_gate); m_tracks[m_trackNo].recGate(m_gate / m_maxGate); break; case "<" : m_octave++; break; case ">": m_octave--; break; case ';': //trace("nexttrack"); m_tracks[++m_trackNo] = createTrack(); m_sequencer.connect(m_tracks[m_trackNo]); break; case '@': atmark(); break; default: break; } } private function getCharNext():String { var c:String = ''; do { c = m_string.charAt(m_letter++); } while (c == ' ' && m_letter < m_string.length); return c; } private function getChar():String { var l:int = m_letter; var c:String = ''; do { c = m_string.charAt(l++); } while (c == ' ' && l < m_string.length); return c; } private function getKeySig():int { var k:int = 0; var f:int = 1; while(f) { var c:String = getChar(); switch(c) { case "+": case "#": k++; m_letter++; break; case "-": k--; m_letter++; break; default: f = 0; break; } } return k; } private function getUInt(def:int):int { var ret:int = 0; var l:int = m_letter; var f:int = 1; while(f) { var c:String = getChar(); switch(c) { case '0': ret = ret * 10 + 0; m_letter++; break; case '1': ret = ret * 10 + 1; m_letter++; break; case '2': ret = ret * 10 + 2; m_letter++; break; case '3': ret = ret * 10 + 3; m_letter++; break; case '4': ret = ret * 10 + 4; m_letter++; break; case '5': ret = ret * 10 + 5; m_letter++; break; case '6': ret = ret * 10 + 6; m_letter++; break; case '7': ret = ret * 10 + 7; m_letter++; break; case '8': ret = ret * 10 + 8; m_letter++; break; case '9': ret = ret * 10 + 9; m_letter++; break; default: f = 0; break; } } return (m_letter == l) ? def : ret; } private function getDot(tick:int):int { var c:String = getChar(); while(c == '.') { m_letter++; tick *= 1.5; c = getChar(); } return tick; } public function createTrack():MTrack { return new MTrack(); } public function play(str:String):void { m_string = str.toLowerCase(); m_sequencer.disconnectAll(); m_tracks = new Array(); m_tracks[0] = createTrack(); m_sequencer.connect(m_tracks[0]); m_trackNo = 0; m_octave = 4; m_velocity = 100; m_length = 4; m_tempo = 120; m_keyoff = 1; m_gate = 15; m_maxGate = 16; m_form = MOscillator.PULSE; m_letter = 0; while(m_letter < m_string.length) { first(); } // 1小節間を空けてからトラック終端記号 for(var i:int = 0; i < m_tracks.length; i++) { m_tracks[i].recRest(384); m_tracks[i].recEOT(); } // play start m_sequencer.play(); } } }MOscillator.as
package com.txt_nifty.sketch.flmml { /** * This class was created based on "MOscillator.as". The original code is in "nu.mine.flashnet.sound.synthesis". */ import de.popforge.audio.output.*; public class MOscillator { public static const SINE:int = 0; public static const SAW:int = 1; public static const TRIANGLE:int = 2; public static const PULSE:int = 3; public static const NOISE:int = 4; public static const MAX:int = 5; protected var m_form:Number; protected var m_frequency:Number; protected var m_phase:Number; public function MOscillator() { m_form = PULSE; m_frequency = 440.0; m_phase = 0.0; } public function setForm(form:int):void { if (form >= MAX) form = MAX-1; m_form = form; } protected function getNextSampleValue():Number { switch(m_form) { case SINE: return Math.sin(m_phase); case SAW: return m_phase / Math.PI - 1.0; case TRIANGLE: return (m_phase < Math.PI) ? (1.0 - 2 * m_phase / Math.PI) : (1.0 - 2 * (2 * Math.PI - m_phase) / Math.PI); case PULSE: return (m_phase < Math.PI) ? 1.0 : -1.0; case NOISE: return Math.random() * 2.0 - 1.0; } return 0.0; } public function getNextSample():Number { var val:Number = getNextSampleValue(); m_phase += (m_frequency * (2 * Math.PI / Audio.RATE44100)); //this should give us a tone of 440hZ. Concert A. if (m_phase > (2 * Math.PI)) { m_phase -= (2 * Math.PI); //prevent phase from overflowing. } return val; } public function setPitch(frequency:Number):void { m_frequency = frequency; } } }MSequencer.as
package com.txt_nifty.sketch.flmml { import de.popforge.audio.output.*; public class MSequencer { private var m_audioBuffer:AudioBuffer; private var m_trackArr:Array; private var m_volume:int; // master volume (max:127) public function MSequencer() { m_audioBuffer = new AudioBuffer(16, Audio.STEREO, Audio.BIT16, Audio.RATE44100); m_audioBuffer.onInit = onAudioBufferInit; m_audioBuffer.onComplete = onAudioBufferComplete; m_trackArr = new Array(); m_volume = 100; } public function play():void { stop(); m_audioBuffer.start(); } public function stop():void { m_audioBuffer.stop(); } public function disconnectAll():void { while(m_trackArr.pop()); } public function connect(track:MTrack):void { m_trackArr.push(track); } private function onAudioBufferInit(buffer:AudioBuffer):void { buffer.start(); } private function onAudioBufferComplete(buffer:AudioBuffer):void { var samples: Array = buffer.getSamples(); var amplitude:Number; var sample:Sample; var i:int; for(i = 0; i < samples.length; i++) { sample = samples[i]; amplitude = 0.0; for each (var track:MTrack in m_trackArr) { amplitude += track.getNextSample(); } sample.left = sample.right = amplitude * m_volume * (0.5 / 127.0); } buffer.update(); var n:int = 0; for (i = 0; i < m_trackArr.length; i++) { if (m_trackArr[i].isEnd()) n++; } if (n >= m_trackArr.length) buffer.stop(); } } }MStatus.as
package com.txt_nifty.sketch.flmml { public final class MStatus { public static const EOT:int = 0; public static const NOP:int = 1; public static const NOTE_ON:int = 2; public static const NOTE_OFF:int = 3; public static const TEMPO:int = 4; public static const VOLUME:int = 5; public static const NOTE:int = 6; public static const FORM:int = 7; public static const ENVELOPE_AD:int = 8; public static const ENVELOPE_SR:int = 9; } }MTrack.as
package com.txt_nifty.sketch.flmml { import de.popforge.audio.output.*; public class MTrack { private var m_ch:MChannel; // channel (instrument) private var m_needle:Number // delta time private var m_bpm:Number; // beat per minute private var m_volume:int; // default volume (max:127) private var m_gate:Number; // default gate time (max:1.0) private var m_events:Array; // private var m_spt:Number; // samples per tick private var m_pointer:int; // current event no. private var m_delta:int; private var m_isEnd:int; private var m_global:int; public function MTrack() { m_isEnd = 0; m_ch = new MChannel(); m_needle = 0.0; playTempo(120); m_volume = 100; m_gate = 15/16; m_events = new Array(); m_pointer = 0; m_delta = 0; m_global = 0; } public function getNextSample():Number { var exec:int = 0; do { exec = 0; if (m_pointer < m_events.length) { var e:MEvent = m_events[m_pointer]; var delta:Number = e.getDelta() * m_spt; if (m_needle >= delta) { trace(m_pointer+"/global:"+(int)(m_global/m_spt)+"/status:"+e.getStatus()+"/delta:"+delta+"-"+e.getDelta()+"/noteNo:"+e.getNoteNo()); exec = 1; switch(e.getStatus()) { case MStatus.NOTE_ON: m_ch.noteOn(MEvent.getFrequency(e.getNoteNo()), e.getVelocity()); break; case MStatus.NOTE_OFF: m_ch.noteOff(); break; case MStatus.NOTE: m_ch.setFrequency(MEvent.getFrequency(e.getNoteNo())); break; case MStatus.EOT: m_ch.noteOff(); m_isEnd = 1; break; case MStatus.NOP: break; case MStatus.TEMPO: playTempo(e.getTempo()); break; case MStatus.VOLUME: break; case MStatus.FORM: m_ch.setForm(e.getForm()); break; case MStatus.ENVELOPE_AD: m_ch.setEnvelopeAD(e.getEnvelopeA(), e.getEnvelopeD()); break; case MStatus.ENVELOPE_SR: m_ch.setEnvelopeSR(e.getEnvelopeS(), e.getEnvelopeR()); break; default: break; } m_needle -= delta; m_pointer++; } } } while(exec); m_needle += 1.0; m_global++; return m_ch.getNextSample() * m_volume * (1.0 / 127.0); } public function seek(delta:int):void { m_delta += delta; } public function recDelta(e:MEvent):void { e.setDelta(m_delta); m_delta = 0; } public function recNote(noteNo:int, len:int, vel:int, keyon:int = 1, keyoff:int = 1):void { var e0:MEvent = new MEvent(); if (keyon) { e0.setNoteOn(noteNo, vel); } else { e0.setNote(noteNo); } recDelta(e0); m_events.push(e0); var e1:MEvent = new MEvent(); if (keyoff) { var gate:int = (int)(len * m_gate); seek(gate); e1.setNoteOff(noteNo, vel); recDelta(e1); m_events.push(e1); seek(len - gate); } else { seek(len); } } public function recRest(len:int):void { seek(len); } public function recVolume(vol:int):void { var e:MEvent = new MEvent(); recDelta(e); e.setVolume(vol); m_events.push(e); } public function recTempo(tempo:Number):void { var e:MEvent = new MEvent(); recDelta(e); e.setTempo(tempo); m_events.push(e); } public function recEOT():void { var e:MEvent = new MEvent(); recDelta(e); e.setEOT(); m_events.push(e); } public function recGate(gate:Number):void { m_gate = gate; } public function recForm(form:int):void { var e:MEvent = new MEvent(); recDelta(e); e.setForm(form); m_events.push(e); } public function recEnvelope(attack:int, decay:int, sustain:int, release:int):void { var e:MEvent = new MEvent(); recDelta(e); e.setEnvelopeAD(attack, decay); m_events.push(e); e = new MEvent(); e.setEnvelopeSR(sustain, release); m_events.push(e); } public function isEnd():int { return m_isEnd; } private function playTempo(bpm:Number):void { m_bpm = bpm; var tps:Number = m_bpm * 96.0 / 60.0; // ticks per second (quater note = 96ticks) m_spt = 44100.0 / tps; // samples per tick trace("spt:"+m_spt) } } }
« 『竜馬がゆく〈1〉』(司馬遼太郎) | トップページ | ActionScript日記 - 高速化 »
「ActionScript 3.0」カテゴリの記事
- FlMML - リングモジュレーターとSync(2009.07.26)
- FlMML - ファミコンDPCM(2009.05.16)
- キー入力のレベル、トリガ、リピートを取得(2009.05.06)
- FlMML - エクスプレッション(2009.05.03)
- FlMML - 引数つきマクロ(2009.04.12)
「FlMML」カテゴリの記事
- FlMMLリポジトリの引越し(2011.02.05)
- FlMML - リングモジュレーターとSync(2009.07.26)
- MML対応Twitterクライアント(2009.07.24)
- FlMML - DPCM変換ツール(2009.05.17)
- FlMML - ファミコンDPCM(2009.05.16)
コメント
この記事へのコメントは終了しました。
トラックバック
この記事へのトラックバック一覧です: ActionScript日記 - サウンドのリアルタイム生成(4):
» [MML]JSMMLで聴いてみて [Nameless Element Lab]
川o・-・)<2nd life - JavaScript から MML を再生する - JSMML http://d.hatena.ne.jp/secondlife/20071006/1191667910 これすげーよ。とりあえずブックマークレットでがんばれ。 選択範囲を演奏するブックマークレット↓ javascript:(function(){var s=document.createE... [続きを読む]
» 【Flash】JavaScriptでMMLを利用する [JavaScript++かも日記]
JavaScript から MML を再生する - JSMML http://d.hatena.ne.jp/secondlife/20071006/1191667910 Textarea Player (Demo) http://svn.coderepos.org/share/lang/javascript/jsmml/trunk/examples/textarea_play.html ActionScript日記 - サウンドのリアルタイム生成(4) http://sketch.txt-nifty... [続きを読む]
はじめまして。音を使って何か作ろうと思いたち、このページにたどり着きました。便利すぎます。コード使わせてもらいます!というか使わせてください!
投稿: kamp | 2007.10.03 05:19
kampさん、はじめまして。
コード、どうぞ使ってください。
ちょっとずつ改良していく予定なので、よければまた見に来てやってください。
そちらの作品が完成したら教えてもらえると嬉しいです!
投稿: おー | 2007.10.04 01:51
おーさん、ありがとうございます!
ちょうど先ほど、公開されました。
http://www.1-click.jp/c10-1.html
ピクセルを音符に見立てて音を鳴らしてます。変な使い方ですみません。。。(上記URLからもこちらのページにリンクを張らせてもらいました。)
作っていてちょっと躓いたところがあったのですが、音の再生位置と、画像のアニメーションを同期させる方法が見つけられず、ちょっと苦労しました。
現在再生時間が取得できると、ビジュアルとの同期が取れ、今回に限らずいろいろと楽しいだろうなあ、と。
そういう値は、FlMMLで取得できるものでしょうか?一応中身を見てはみたのですが、力不足でよくわからないのです。。。
なにはともあれ、ありがとうございました。popforgeを見ても難しくてよく分からなかったので、FlMMLがなければ、計画倒れで終わってしまうところでした。
投稿: kamp | 2007.10.04 19:29
おおー仕事が早いですね!
早速作品拝見しました。
最初は意味が分からずキョトンとしてたんですが、二段階目、三段階目とドンドン面白くなっていきますね!
自分の作ったものが使ってもらえてるっていうのも嬉しいです。
ユーザに書いてもらった絵に対して音を鳴らすようなことも出来そうですね。
1-click Awardっていうのも面白そうです。もし何か作れたらエントリーしてみたいです。
そして、そうですよね。ビジュアルと同期、したいですよね。いまのままだと 0.74秒に一回しか同期取れないんですよねぇ。
簡単に説明しますと、popforgeでは2048サンプルを最短処理単位としています。サンプルレート44100Hzのときは0.046秒単位ってことですね。
詳しいことはこちらに。
http://web.archive.org/web/20030204140141/http://www.active-web.cc/html/research/f6sync/f6sync.txt
ってことは0.046秒ごとに同期が取れる理屈なんですが、実際はそれだと処理が間に合わないのでもう少し大きな単位を使っています。
new AudioBuffer(16, ...)
と指定してる箇所があるんで、FlMMLの場合は16×2048=32768。44100で割って0.74秒ですね。
まあ手元にある最新版では処理速度も上がってますし、今回作っていただいた作品では単音ですからもう少し処理単位を小さくしてもいいんですけれど、何か汎用的に使える仕組みが欲しいですね。
ちょっとそのあたり考えてみたいと思います。
指定したタイミングごとに割り込みが入れられたらいいのかな? という気もするんですがどうなんでしょうね?
また何かご意見などあったらお聞かせください。
投稿: おー | 2007.10.05 02:23
見ていただいてありがとうございます!
ユーザーも自由に写真をアップロードできるよう、機能追加しようと思ってます。
音も、ホワイトノイズを定期的に入れて小節感をだし、もう少し音楽っぽく聞こえるようにならないかな、と思ってます。
同期についての説明ありがとうございます。
ではFlMMLだったら、
MSequencer.onAudioBufferComplete
が呼ばれたら0.74秒再生された、と判断すればいいということでしょうか。
ビジュアルとの同期関係で、あったら嬉しい機能についてですが、
BPMにあわせてイベントを発信してもらって、
MML.addEventListener(MML.EVENT_BEAT, onBeat)
みたいなことができるといいなあ、と。
MML言語自体を拡張して、このタイミングでイベント発信、と記述できてしまうのも素敵です!
あるいは、MML.positionとかで、再生からの秒数を取れるだけでも嬉しいです。
ワガママばかりいってますが、参考になれば幸いですー
投稿: | 2007.10.05 17:48
そうです。そのはず。試してないけど(^^;)<MSequencer.onAudioBufferComplete
なるほど。addEventListenerですか。
それで実装するのがActionScriptっぽくていいんでしょうね。なんか継承したほうがいいのかとか、イマイチまだ分かってないんですが、なんとかやってみたいと思います。
またアドバイスくださいませー。
投稿: おー | 2007.10.06 00:48
初めまして。素晴らしいライブラリの公開、ありがとうございます。
あまりにも簡単に Flash から MML の演奏が出来、驚きです。
JavaScript からも MML を鳴らせたらそれはれそれで便利だなぁ、と思いJavaScript からお手軽に FLMML を使うためのブリッジの JSMML というのを作ってみました。
- http://coderepos.org/share/wiki/JSMML
- http://svn.coderepos.org/share/lang/javascript/jsmml/trunk/examples/textarea_play.html (動作例)
FLMML のソースコード自体は同梱しておらず、コンパイルした swf を配布しています。もしライセンスなどで問題があったら教えてください。
(よろしければ、FLMML のライセンスも教えていただけると嬉しいです)
また、一部コードにパッチを当てて利用しています。
- http://svn.coderepos.org/share/lang/javascript/jsmml/trunk/patch/flmml.diff
- MML の private な名前空間の一部を protected
- MML/MSequencer が EventDispatcher を継承
- MSequencer の再生が終わったら Event.COMPLETE が飛ぶように
- MSquence で大まかな(0.74秒単位) 再生時間を表す now を追加
を変更して利用させて貰ってます。
投稿: yuichi tateno | 2007.10.06 15:25
yuichi tatenoさん、はじめまして!
すごい! JavaScriptから動いてますね。
これは面白いなー。
Google Mapで蒲田を見たら蒲田行進曲流したりできるってことですよね。もっと面白いことも出来そうで楽しみ。
FlMMLのライセンスですか。
修正BSDでもいい気もするんですが、popforgeがGPL2なんで、GPL2にしておくのがいいんですかね。
あんまりライブラリ公開とか慣れてないんで、こんなんでいいのか分からないけど(^^;)
修正していただいたコード、参考にさせてもらいます!
いろんなひとに見に来ていただいてるのに、なかなか次のエントリが書けなくてすみません~。
投稿: おー | 2007.10.06 16:57
こんばんは。レスありがとうございます。
> Google Mapで蒲田を見たら蒲田行進曲流したりできるってことですよね。もっと面白いことも出来そうで楽しみ。
ですね。blog の内容にあわせて曲を生成したり、いろんなことが出来そうです!
> 修正BSDでもいい気もするんですが、popforgeがGPL2なんで、GPL2にしておくのがいいんですかね。
なるほど、popforge が GPL2 なんですね。popforge のコードを流用してる場合は GPL2 にしないとまずいのですが、コピペなんかをしてないのでしたら、お好きなライセンスで良いと思いますよ~。
投稿: yuichi tateno | 2007.10.06 19:28
こんばんは。
再生時間を細かく取れるように改造中なんですが苦戦中です(--;)
yuichi tatenoさんて、id:secondlifeさんだったんですね! いつもblog拝見しております。そしてfcwrapを使わせて頂いてますm(__)m
blogの内容にあわせて曲を生成って面白いですね。沖縄っぽいキーワードを見つけたら琉球音階のメロディを生成したりとかしたら、それっぽくなりそう。誰か作ってくれないかなぁ。
なるほど。それじゃより緩いほうってことで、修正BSDにしようかと思います。
http://www.flashbrighton.org/wordpress/?p=9
こちらのコードはもろに参考にしちゃってるんですが、こっちは "no licence" って書いてくれてますしね。ありがたいありがたい。
投稿: おー | 2007.10.08 01:51