開発環境 Apache Flex SDK 4.12.1
FlashDevelop 4.6.2.5
実行環境 Microsoft Windows 8.1 (64bit)
プロジェクトの種類 ActionScript 3/AS3 Project
プロジェクト名 EzFM


Output/General
Dimensions 800 x 300 px

Main.as
package 
{
    import flash.display.*;
    import flash.events.*;
    import flash.media.*;
    import flash.text.*;
    import mx.utils.*;
 
    public class Main extends Sprite 
    {
        private var tfprm:TextField;
        private var tfmml:TextField;
        private var envelop:Vector.<Sprite>;
 
        private var op:Vector.<Operator>;
        private var mml:String;
        private var pos:int; // position
        private var waveform:Vector.<Number>;
 
        private const scale:Object = { A:9, B:11, C:0, D:2, E:4, F:5, G:7 }; // 音階
        private const timebase:int = 960;
        private var seq:Vector.<Object>; // sequence
        private var atick:int; // absolute tick
        private var deflen:int; // default length
        private var octave:int;
        private var q:int; // 1音中の音の長さの割り合い
        private var tempo:int;
 
        private var sound:Sound = new Sound;
        private var soundChannel:SoundChannel = null;
 
        public function Main():void 
        {
            if (stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
 
        private function init(e:Event = null):void 
        {
            removeEventListener(Event.ADDED_TO_STAGE, init);
            // entry point
 
            createLabel(10, 10, "Op.Params:");
            tfprm = createInput(10, 30, 380, 100,
                "OP\tAR\tDR\tSR\tRR\tSL\tOL\tMul\n" +
                "1\t31\t31\t1\t0\t3\t0\t1\n" +
                "2\t31\t31\t1\t0\t3\t0\t1\n");
 
            createLabel(10, 140, "MML:");
            tfmml = createInput(10, 160, 380, 50, "o4cdefgr2");
 
            var play:TextField = createButton(100, 220, 80, 30, "Play");
            var stop:TextField = createButton(200, 220, 80, 30, "Stop");
 
            envelop = new Vector.<Sprite>(2, true);
            for (var i:int = 0; i < envelop.length; i++) 
            {
                var sp:Sprite = new Sprite;
                sp.x = 450;
                sp.y = 10 + 120 * i;
                addChild(sp);
                envelop[i] = sp;
            }
 
            op = new Vector.<Operator>(2, true);
 
            sound = new Sound;
            sound.addEventListener(SampleDataEvent.SAMPLE_DATA, onSampleData);
 
            play.addEventListener(MouseEvent.CLICK, onPlay);
            stop.addEventListener(MouseEvent.CLICK, onStop);
        }
 
        // ラベル作成
        private function createLabel(x:Number, y:Number, text:String):void 
        {
            var tf:TextField = new TextField;
            tf.autoSize = TextFieldAutoSize.LEFT;
            tf.defaultTextFormat = new TextFormat(null, 16);
            tf.x = x;
            tf.y = y;
            tf.text = text;
            addChild(tf);
        }
 
        // 入力作成
        private function createInput(x:Number, y:Number, width:Number, height:Number, text:String):TextField
        {
            var tf:TextField = new TextField;
            tf.border = true;
            tf.defaultTextFormat = new TextFormat("_typeWriter", 16);
            tf.multiline = true;
            tf.type = TextFieldType.INPUT; // 編集可
            tf.x = x;
            tf.y = y;
            tf.width = width;
            tf.height = height;
            tf.text = text;
            addChild(tf);
            return tf;
        }
 
        // ボタン作成
        private function createButton(x:Number, y:Number, width:Number, height:Number, text:String):TextField
        {
            var textFormat:TextFormat = new TextFormat;
            textFormat.size = 24;
            textFormat.align = TextFormatAlign.CENTER;
 
            var tf:TextField = new TextField;
            tf.border = true;
            tf.defaultTextFormat = textFormat;
            tf.selectable = false;
            tf.x = x;
            tf.y = y;
            tf.width = width;
            tf.height = height;
            tf.text = text;
            addChild(tf);
            return tf;
        }
 
        // 再生ボタン
        private function onPlay(e:MouseEvent):void 
        {
            parseParams();
 
            seq = new Vector.<Object>;
            atick = 0;
            deflen = 4;
            octave = 4;
            q = 7;
            tempo = 120;
 
            mml = tfmml.text.toUpperCase();
            for (pos = 0; pos < mml.length; ) 
            {
                var cmd:String = mml.charAt(pos++);
                switch (cmd) 
                {
                    case "<":
                        octave--;
                        break;
                    case ">":
                        octave++;
                        break;
                    case "A": case "B": case "C": case "D": case "E": case "F": case "G":
                        note(cmd);
                        break;
                    case "L": // 1~64
                        deflen = getValue();
                        break;
                    case "O": // 1~8
                        octave = getValue();
                        break;
                    case "Q": // 1~8
                        q = getValue();
                        break;
                    case "T": // 32~255
                        tempo = getValue();
                        break;
                    case "R": // 1~64
                        rest();
                        break;
                }
            }
 
            var length:uint = tick2sample(atick);
            waveform = new Vector.<Number>(length, true);
            for each (var data:Object in seq) 
            {
                generateNote(data);
            }
 
            var st:SoundTransform = new SoundTransform;
            st.volume = 0.3;
            soundChannel = sound.play(0, 0, st);
        }
 
        // 停止ボタン
        private function onStop(e:MouseEvent):void 
        {
            if (soundChannel == null) return;
 
            soundChannel.stop();
            soundChannel = null;
        }
 
        // サンプルデータ要求
        private function onSampleData(e:SampleDataEvent):void 
        {
            var len:int = Math.min(waveform.length - e.position, 8192);
            for (var i:int = 0; i < len; i++) 
            {
                var value:Number = waveform[e.position + i];
                e.data.writeFloat(value);
                e.data.writeFloat(value);
            }
        }
 
        // パラメタ解析
        private function parseParams():void 
        {
            var str:String = tfprm.text;
 
            str = StringUtil.trim(str);
            var rec:Array = str.split("\r");
            for (var i:int = 0; i < 2; i++) 
            {
                var fld:Array = rec[1 + i].split("\t");
                var o:Operator = new Operator;
                o.AR = fld[1];
                o.DR = fld[2];
                o.SR = fld[3];
                o.RR = fld[4];
                o.SL = fld[5];
                o.OL = fld[6];
                o.Mul = fld[7];
                op[i] = o;
 
                drawEnvelop(envelop[i], o);
            }
        }
 
        private function drawEnvelop(sp:Sprite, o:Operator):void 
        {
            var g:Graphics = sp.graphics;
            g.clear();
            g.beginFill(0xffbf7f);
            g.drawRect(0, 0, 200, 100);
            g.endFill();
            g.beginFill(0xffff7f);
            g.drawRect(200, 0, 50, 100);
            g.endFill();
 
            var ax:int = (1.0 - o.AR / 32) * 50;
            var ay:Number = 1.0;
            var dx:int = ax + (1.0 - o.DR / 32) * 50;
            var dy:Number = 1.0 - o.SL / 15;
            var sx:int = dx;
            var sy:Number = dy;
            for ( ; sx < 200; sx++) 
            {
                sy -= (o.SR / 32) / 50;
                if (sy < 0.0)
                {
                    sy = 0.0;
                    break;
                }
            }
            var rx:int = sx + sy * (1.0 - o.RR / 16) * 50;
            var ry:Number = 0.0;
 
            g.lineStyle(1);
            g.moveTo(0, fy(0.0));
            g.lineTo(ax, fy(ay));
            g.lineTo(dx, fy(dy));
            g.lineTo(sx, fy(sy));
            g.lineTo(rx, fy(ry));
        }
 
        private function fy(y:Number):Number
        {
            return (1 - y) * 100;
        }
 
        private function tick2sample(tick:int):int
        {
            return (tick / timebase) * (60 / tempo) * 44100;
        }
 
        // 音生成
        private function generateNote(data:Object):void 
        {
            trace(StringUtil.substitute("t:{0} g:{1} n:{2}", data.tick, data.gate, data.noteno));
 
            var freq:Number = 440 * Math.pow(Math.pow(2, data.noteno - 69), 1 / 12);
            var keyOff:int = tick2sample(data.gate);
            var base:int = tick2sample(data.tick);
 
            keyOn(true);
            for (var i:int = 0; ; i++) 
            {
                if (i == keyOff) keyOn(false);
 
                var t:Number = (i * freq / 44100) % 1;
                var h:Number = 0;
 
                // OP.1
                h = op[0].calc(t, h);
                h *= 2 * Math.PI * (1.0 - op[0].OL / 128);
 
                // OP.2
                h = op[1].calc(t, h);
 
                try
                {
                    waveform[base + i] = h;
                }
                catch (err:Error)
                {
                    return;
                }
 
                if (op[1].isOff()) break;
            }
        }
 
        private function keyOn(on:Boolean):void 
        {
            for each (var o:Operator in op) 
            {
                o.keyOn(on);
            }
        }
 
        private function seqData(data:Object):void 
        {
            data.tick = atick;
            seq.push(data);
        }
 
        // 音符
        private function note(cmd:String):void 
        {
            var len:int = getValue();
            var dot:int = getDot();
 
            var noteno:int = (octave + 1) * 12 + scale[cmd];
            var tick:int = calcTick(len, dot);
            var gate:int = tick * q / 8;
            seqData( { "noteno":noteno, "gate":gate } );
            atick += tick;
        }
 
        // 休符
        private function rest():void 
        {
            var len:int = getValue();
            var dot:int = getDot();
 
            var tick:int = calcTick(len, dot);
            atick += tick;
        }
 
        // tick計算
        private function calcTick(len:int, dot:int):int
        {
            if (len == 0) len = deflen;
            var tick:int = timebase * 4 / len;
            var n:int = Math.pow(2, dot);
            tick += tick * (n - 1) / n;
            return tick;
        }
 
        // 値
        private function getValue():int
        {
            var value:int = 0;
            for ( ; pos < mml.length; pos++) 
            {
                var num:int = mml.charCodeAt(pos) - 0x30;
                if (num < 0 || 9 < num) break;
                value = value * 10 + num;
            }
            return value;
        }
 
        // 付点
        private function getDot():int 
        {
            var dot:int = 0;
            for ( ; pos < mml.length; pos++) 
            {
                if (mml.charAt(pos) != ".") break;
                dot++;
            }
            return dot;
        }
 
    }
 
}
 

Operator.as
package  
{
    public class Operator 
    {
        // EGPhase
        private const off:int = 0;
        private const attack:int = 1;
        private const decay:int = 2;
        private const sustain:int = 3;
        private const release:int = 4;
 
        // 音色設定パラメタ
        public var AR:int; // 0.Attack Rate(0~31)
        public var DR:int; // 1.Decay Rate(0~31)
        public var SR:int; // 2.Sustain Rate(0~31)
        public var RR:int; // 3.Release Rate(0~15)
        public var SL:int; // 4.Sustain Level(0~15)
        public var OL:int; // 5.Output Level(127~0)
        public var Mul:int; // 7.Multiple(0~15)
        // 6.Keyboard Rate Scaling Depth
        // 8.Detune
        // 9.Amplitude Modulation Sensitivity
 
        // Envelop Generator
        private var phase:int; // EGPhase
        private const cmax:int = 44100; // count max
        private var count:int;
        private var egol:Number; // EG出力レベル(0.0~1.0)
        private var sol:Number; // sustain output level
        private var mul:Number; // multiple
 
        public function Operator() 
        {
        }
 
        public function keyOn(on:Boolean):void 
        {
            if (on)
            {
                phase = attack;
                count = 0;
                egol = 0.0;
                sol = (16 - SL) / 16;
                mul = (Mul == 0) ? 0.5 : Mul;
            }
            else
            {
                phase = release;
                count = cmax * egol;
            }
        }
 
        public function calc(t:Number, h:Number):Number
        {
            switch (phase) 
            {
                case attack:
                    count += 1 + AR;
                    if (count < cmax)
                    {
                        egol = count / cmax;
                    }
                    else
                    {
                        phase = decay;
                        count = 0;
                        egol = 1.0;
                    }
                    break;
 
                case decay:
                    count += 1 + DR;
                    if (count < cmax)
                    {
                        var x:Number = count / cmax;
                        egol = (1.0 - x) + sol * x;
                    }
                    else
                    {
                        phase = sustain;
                        egol = sol;
                        count = cmax * egol;
                    }
                    break;
 
                case sustain:
                    count -= SR;
                    if (count <= 0)
                    {
                        count = 0;
                        phase = off;
                    }
                    egol = count / cmax;
                    break;
 
                case release:
                    count -= 1 + RR;
                    if (count <= 0)
                    {
                        count = 0;
                        phase = off;
                    }
                    egol = count / cmax;
                    break;
            }
 
            h = Math.sin(2 * Math.PI * mul * t + h);
            return h * egol;
        }
 
        public function isOff():Boolean
        {
            return phase == off;
        }
 
    }
 
}
 
最終更新:2014年08月29日 16:28