開発環境 | Apache Flex SDK 4.12.1 |
FlashDevelop 4.6.2.5 | |
実行環境 | Microsoft Windows 8.1 (64bit) |
プロジェクトの種類 | ActionScript 3/AS3 Project |
プロジェクト名 | EzFM |
Dimensions | 800 x 300 px |
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;
}
}
}
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;
}
}
}