Beautifl - Flash Gallery

Thumbnail : MIDIシーケンスを演奏してみた
MIDIシーケンスを演奏してみた
keim_at_Si 2010-01-09 MIT License

再生するにはFlash Playerが必要です。デスクトップのブラウザでご覧ください。

// forked from nemu90kWw's MIDIシーケンスを解析してみた
//
// デフォルトのMIDIは,kashiwa@正直日記さんのものをお借りしました.
// 最初に,ドラムをプリレンダリングするため,少し待ってください.
// ほんのちょっと複雑なMIDIファイルを鳴らすだけで,
// 誰でも簡単にブラウザクラッシュができます.
package
{
	import flash.display.*;
	import flash.events.*;
	import flash.text.*;
	import flash.net.*;
	import flash.utils.ByteArray;
	import mx.utils.Base64Decoder;
	
	[SWF(width="465", height="465", frameRate="24")]
	public class main extends Sprite
	{
		private var button:Sprite;
		private var playButton:Sprite, stopButton:Sprite;
		private var fileReference:FileReference;
		
		private var textfield:TextField = new TextField();
		private var information:TextField = new TextField();
		
		private var midi:SMFSequence;
	    
	    private var midiPlayer:MIDIPlayer;
		
		function main()
		{
			textfield.width = 465
			textfield.height = 465-30;
			textfield.y = 30;
			textfield.wordWrap = true;
			addChild(textfield);
			
		    information.width = 232;
			information.height = 30;
		    information.x = 235;
		    information.y = 10;
		    addChild(information);
		    
			var decoder:Base64Decoder = new Base64Decoder();
			decoder.decode(SMFBinary.data);
			
			var ba:ByteArray = decoder.toByteArray();
			ba.uncompress();
			
			midi = new SMFSequence(ba);
			textfield.text = midi.toString();
			
		    button     = b(5, 5, "Open SMF");
		    playButton = b(130, 5, "Play");
		    stopButton = b(130, 5, "Stop");
		    stopButton.visible = false;
		    function b(x:Number, y:Number, label:String) : Sprite {
		        var button:Sprite = new Sprite(),
    		        buttontext:TextField = new TextField();
		        
			button.x = x;
			button.y = y;
			button.mouseChildren = false;
			button.buttonMode = true;
			button.graphics.lineStyle(1, 0xBBBBBB);
			button.graphics.beginFill(0xEEEEEE);
			button.graphics.drawRoundRect(0, 0, 100, 20, 5, 5);
			button.graphics.endFill();
			addChild(button);
			
			buttontext.width = 100;
			buttontext.height = 20;
			buttontext.htmlText = "<p align='center'><font face='_sans'>" + label + "</span></p>";
			button.addChild(buttontext);
			
		        return button;
		    }
		    
			fileReference = new FileReference();
			fileReference.addEventListener(Event.SELECT, onSelect);
			fileReference.addEventListener(Event.COMPLETE, onComplete);
			button.addEventListener(MouseEvent.CLICK, onClick);
		    playButton.addEventListener(MouseEvent.CLICK, onPlay);
		    stopButton.addEventListener(MouseEvent.CLICK, onStop);
		    addEventListener(Event.ENTER_FRAME, onEnverFrame);
		    
		    midiPlayer = new MIDIPlayer();
		}
		
		private function onClick(event:MouseEvent):void 
		{
			fileReference.browse([new FileFilter("MIDIシーケンス(mid)", "*.mid")]);
		}
	    
		private function onSelect(event:Event):void
		{
			fileReference.load();
		}
		
		private function onComplete(event:Event):void
		{
			midi = new SMFSequence(fileReference.data);
			textfield.text = midi.toString();
		}
	    
	    private function onPlay(event:MouseEvent):void 
	    {
	        midiPlayer.play(midi);
	        playButton.visible = false;
	        stopButton.visible = true;
	    }
	    
	    private function onStop(event:MouseEvent):void 
	    {
	        driver.stop();
	        playButton.visible = true;
	        stopButton.visible = false;
	    }
	    
	    private function onEnverFrame(event:Event):void
	    {
	        information.text = "position : " + (driver.position*0.001).toFixed(1) + "[sec]";
	    }
	}
}




import flash.text.*;
import org.si.sion.*;
import org.si.sion.sequencer.SiMMLTrack;
import org.si.sion.utils.SiONPresetVoice;
import org.si.sion.namespaces._sion_internal;

var driver:SiONDriver;

var trackCountLimit:int = 24;
var freePointer:int = 0;
function freeNoteOffTracks() : void {
    var tr:SiMMLTrack, activeTracks:int, i:int, imax:int = driver.trackCount;
    for (activeTracks=0, i=0; i<imax; i++) {
        tr = driver.sequencer.tracks[i];
        if (tr.isActive) activeTracks++;
    }
    if (activeTracks > trackCountLimit) {
        for (activeTracks=0, i=0; i<imax; i++) {
            if (++freePointer == imax) freePointer = 0;
            tr = driver.sequencer.tracks[freePointer];
            if (!tr.channel.isIdling && !tr.channel.isNoteOn()) {
                tr.channel.reset();
                if (--activeTracks == trackCountLimit) return;
            }
        }
        for (i=activeTracks - trackCountLimit; i>=0; --i) {
            if (++freePointer == imax) freePointer = 0;
            tr = driver.sequencer.tracks[freePointer];
            tr.keyOff();
            tr.channel.reset();
        }
    }
}

class MIDIPlayer {
    public  var voices:SiONPresetVoice;
    private var _tickPerClock:Number;
    private var _channels:Vector.<MIDIChannel> = new Vector.<MIDIChannel>(16);
    private var _pointers:Vector.<SMFTrackPointer> = new Vector.<SMFTrackPointer>();
    
    function MIDIPlayer() {
        driver = SiONDriver.mutex || new SiONDriver();
        voices = new SiONPresetVoice();
        MIDIChannel.midiVoiceList = voices["midi"];
        _createGMDrumSet();
        for (var i:int=0; i<16; i++) _channels[i] = new MIDIChannel(i);
    }
    
    public function play(smfData:SMFSequence) : void {
        _pointers.length = smfData.tracks.length;
        for (var i:int=0; i<_pointers.length; i++) {
            _pointers[i] = new SMFTrackPointer(smfData.tracks[i]);
        }
        
        SMFTrackPointer.tickPerClock = smfData.division/8;
        driver.setTimerInterruption(0.5, _onMIDIClock);
        driver.bpm = smfData.tempo;
        
        reset();
        driver.play("#EFFECT1{reverb};#EFFECT2{chorus};");
    }
    
    private function _createGMDrumSet() : void {
        var perc:Array = voices["valsound.percus"], tom:SiONVoice = new SiONVoice(), wave:Vector.<Number>;
        var mml:String = "@v128%6";
        for (var i:int=0; i<perc.length; i++) driver.setVoice(i, perc[i]);
        // bd
        wave = driver.render(mml+"v20@0o2c2");
        driver.setSamplerData(35, wave);
        driver.setSamplerData(36, wave);
        // sd
        driver.setSamplerData(37, driver.render(mml+"v20@26o4c2"));
        driver.setSamplerData(38, driver.render("#EFFECT{ws};@v128%6v16@30o3c2"));
        driver.setSamplerData(39, driver.render(mml+"v20@26o5g2"));
        driver.setSamplerData(40, driver.render("#EFFECT{ws};@v128%6v16@14o2g2"));
        // tom
        tom.param = [8,0,0,19,60,48,0,48,15,12,0,0,1,0,0,0,0,68,17,48,8,0,0,0,0,0,0,1,0,0,0,0,68,34,60,45,0,0,8,26,0,0,1,0,300,0,0,0,32,60,24,0,24,15,0,0,0,1,0,0,0,0,0];
        tom.setLPFEnvelop(100);
        driver.setVoice(64, tom);
        driver.setSamplerData(41, driver.render(mml+"v24@64o3c"));
        driver.setSamplerData(43, driver.render(mml+"v24@64o3e"));
        driver.setSamplerData(45, driver.render(mml+"v24@64o3g"));
        driver.setSamplerData(47, driver.render(mml+"v24@64o4c"));
        driver.setSamplerData(48, driver.render(mml+"v24@64o4g"));
        driver.setSamplerData(50, driver.render(mml+"v24@64o4b"));
        // hh
        driver.setSamplerData(42, driver.render(mml+"p2v6@17o4c"));
        driver.setSamplerData(44, driver.render(mml+"p2v6@15o4c"));
        driver.setSamplerData(46, driver.render(mml+"p2v6@24o5f1"));
        // cc
        driver.setSamplerData(49, driver.render(mml+"v16@7o5c1^1"));
        driver.setSamplerData(51, driver.render(mml+"v16@27o6f+1^1"));
        driver.setSamplerData(52, driver.render(mml+"v16@7o2g1^1"));
        driver.setSamplerData(55, driver.render(mml+"v16@7o5c1^1"));
        driver.setSamplerData(57, driver.render(mml+"v16@7o5f1^1"));
    }
    
    public function reset() : void {
        for (var i:int=0; i<16; i++) _channels[i].reset();
    }
    
    private function _onMIDIClock() : void {
        freeNoteOffTracks();
        var e:SMFEvent, pt:SMFTrackPointer;
        for each (pt in _pointers) {
            while (e = pt.getNextEvent()) {
                switch(e.type) {
                case 0x80: _channels[e.channel].noteOff(e.note); break;
                case 0x90: _channels[e.channel].noteOn(e.note, e.velocity); break;
                case 0xa0: _channels[e.channel].channelAfterTouch(e.value); break;
                case 0xb0: _channels[e.channel].controlChange(e.cc, e.value); break;
                case 0xc0: _channels[e.channel].programChange(e.value); break;
                case 0xd0: _channels[e.channel].channelAfterTouch(e.value); break;
                case 0xe0: _channels[e.channel].pitchBend(e.lsb | (e.msb<<7)); break;
                case 0x51: driver.bpm = 60000000/e.tempo; break;
                }
            }
        }
    }
}

class SMFTrackPointer {
    static public var tickPerClock:Number;
    public var smfSequence:Vector.<SMFEvent>, pointer:int=0, ticks:Number=0;
    function SMFTrackPointer(track:SMFTrack) { smfSequence = track.sequence; }
    public function getNextEvent() : SMFEvent {
        if (pointer >= smfSequence.length) return null;
        var e:SMFEvent = smfSequence[pointer];
        ticks += e.delta_time;
        if (ticks >= tickPerClock) {
            ticks -= (e.delta_time + tickPerClock);
            return null;
        }
        pointer++;
        return e;
    }
}

class MIDIChannel {
    use namespace _sion_internal;
    static public var midiVoiceList:Array;
    static private var _pbRate:Number = 1/8192*64;
    
    public var drumChannel:Boolean = false, mute:Boolean = false;
    public var volumes:Vector.<int>, exp:int, pan:int, centerPitch:int;
    public var programNum:int, pb:int, afterTouch:int, modulation:int;
    public var rpn:int, pbRange:int, fineTune:int, courseTune:int;
    private var _trackID:int;
    
    function MIDIChannel(channelNumber:int) {
        _trackID = channelNumber;
        drumChannel = (channelNumber == 9);
    }
    
    public function reset() : void {
        volumes = Vector.<int>([100, 0, 0, 0, 0, 0, 0, 0]);
        programNum = 0;
        exp = 128;
        pan = 64;
        pb = 0;
        afterTouch = 0;
        modulation = 0;
        rpn = 0;
        pbRange = 2;
        fineTune = 64;
        courseTune = 64;
    }
    
    public function noteOff(note:int) : void {
        if (mute) return;
        driver.noteOff(note, _trackID, 0, 0);
    }
    
    public function noteOn(note:int, vel:int) : void {
        var track:SiMMLTrack;
        if (mute) return;
        if (vel == 0) noteOff(note);
        else {
            if (drumChannel) track = driver.playSound(note, 0, 0, 0, _trackID);
            else track = driver.noteOn(note, midiVoiceList[programNum], 0, 0, 0, _trackID);
            track.noteShift  = courseTune - 64; // not available
            track.pitchShift = fineTune - 64;   // not available
            track.velocity = vel + afterTouch;
            track.expression = exp;
            track.channel.setAllStreamSendLevels(volumes);
            track.channel.pan = pan-64;
            track.channel.setPitchModulation(modulation);
            if (pb != 0) track.channel.pitch += pb * pbRange * _pbRate;
        }
    }
    
    public function controlChange(cc:int, value:int) : void {
        switch (cc) {
        case 1:  modulation = value; break;
        case 6:  dataEntry(value); break;
        case 7:  volumes[0] = value; break;
        case 10: pan = value; break;
        case 33: modulation = value; break;
        case 91: volumes[1] = value; break;
        case 93: volumes[2] = value; break;
        case 100: rpn = (rpn & 0xff00) | value;      break; // LSB
        case 101: rpn = (rpn & 0x00ff) | (value<<8); break; // MSB
        case 2: case 11:
            exp = value; 
            forEachTracks(function(tr:SiMMLTrack):void{tr.expression = exp;});
            break;
        }
        
        function dataEntry(value:int) : void {
            switch (rpn) {
            case 0x0000: pbRange    = value; break;
            case 0x0001: fineTune   = value; break;
            case 0x0002: courseTune = value; break;
            }
        }
    }
    
    public function programChange(pn:int) : void {
        programNum = pn;
    }
    
    public function channelAfterTouch(value:int) : void {
        if (mute) return;
        var atDiff:int = afterTouch - value;
        forEachTracks(function(tr:SiMMLTrack) : void { tr.velocity += atDiff; });
        afterTouch = value;
    }
    
    public function pitchBend(value:int) : void {
        var pitchDiff:int = (value - 8192 - pb) * pbRange * _pbRate;
        forEachTracks(function(tr:SiMMLTrack) : void { tr.channel.pitch += pitchDiff; });
        pb = value - 8192;
    }
    
    public function forEachTracks(func:Function) : void {
        // The tracks created by noteOn have an internal track id of "trackID | DRIVER_NOTE_ID_OFFSET".
        var internalTrackID:int = _trackID | SiMMLTrack.DRIVER_NOTE_ID_OFFSET;
        for each (var tr:SiMMLTrack in driver.sequencer.tracks) {
            if (tr.isActive && tr.trackID == internalTrackID) func(tr);
        }
    }
}




	import flash.utils.ByteArray;
	
	class SMFSequence
	{
		public var format:int;
		public var numTracks:int;
		public var division:int;
		public var tempo:int = 0;
		public var title:String = "";
		public var artist:String = "";
		public var signature_n:int;
		public var signature_d:int;
		public var length:int;
		
		public var tracks:Vector.<SMFTrack> = new Vector.<SMFTrack>();
		
		function SMFSequence(bytes:ByteArray)
		{
			bytes.position = 0;
			
			while(bytes.bytesAvailable > 0)
			{
				var type:String = bytes.readMultiByte(4, "us-ascii");
				switch(type)
				{
				case "MThd":	//ヘッダ
					bytes.position += 4;	//ヘッダのデータ長は常に00 00 00 06なのでスルー
					format = bytes.readUnsignedShort();
					numTracks = bytes.readUnsignedShort();
					division = bytes.readUnsignedShort();
					break;
				case "MTrk":	//トラック
					var len:uint = bytes.readUnsignedInt();
					var temp:ByteArray = new ByteArray();
					bytes.readBytes(temp, 0, len);
					var track:SMFTrack = new SMFTrack(this, temp);
					tracks.push(track);
					length = Math.max(length, track.length);
					break;
				default:
					return;
				}
			}
		}
		
		public function toString():String
		{
			var text:String = "format : "+format+" | numTracks : "+numTracks+" | division : "+division+"\n";
			text += "タイトル : "+title+" | 著作権表示 : "+artist+"\n";
			text += "拍子 : "+signature_d+"分の"+signature_n+"拍子 | BPM : "+tempo+" | length : "+length+"\n";
			
			text += "\n";
			
			for(var i:int = 0; i < tracks.length; i++)
			{
				text += "トラック"+i+" : "+tracks[i].toString() + "\n";
			}
			
			return text;
		}
	}

	class SMFTrack
	{
		public var parent:SMFSequence;
		public var sequence:Vector.<SMFEvent> = new Vector.<SMFEvent>();
		public var length:int;
		
		function SMFTrack(parent:SMFSequence, bytes:ByteArray)
		{
			this.parent = parent;
			
			var event:SMFEvent;
			var temp:int;
			var len:int;
			
			var type:int;
			var channel:int;
			
			var time:int;
			/*
			var readVariableLength:Function = function(time:uint = 0):uint
			{
				var temp:uint = bytes.readUnsignedByte();
				if(temp & 0x80) {return readVariableLength(time + (temp & 0x7F));}
				else {return time + (temp & 0x7F);}
			}
			*/
			var readVariableLength:Function = function(time:uint = 0):uint
			{
				var temp:uint = bytes.readUnsignedByte();
				if(temp & 0x80) {return readVariableLength((time << 7) + (temp & 0x7F));}
				else {return (time << 7) + (temp & 0x7F);}
			}
			
			main : while(bytes.bytesAvailable > 0)
			{
				event = new SMFEvent();
				event.delta_time = readVariableLength();
				time += event.delta_time;
				event.time = time;
				
				temp = bytes.readUnsignedByte();
				
				if(temp == 0xFF)
				{
					event.type = bytes.readUnsignedByte();
					len = readVariableLength();
					
					switch(event.type)
					{
					case 0x02:	//作者
						event.artist = bytes.readMultiByte(len, "Shift-JIS");
						parent.artist = event.artist;
						break;
					case 0x03:	//タイトル
						event.title = bytes.readMultiByte(len, "Shift-JIS");
						parent.title = event.title;
						break;
					case 0x2F:	//トラック終了
						break main;
					case 0x51:	//テンポ
						event.tempo = bytes.readUnsignedByte()*0x10000 + bytes.readUnsignedShort();
						if(parent.tempo == 0) {
							parent.tempo = 60000000 / event.tempo;
						}
						break;
					case 0x58:	//拍子
						parent.signature_n = bytes.readUnsignedByte();
						parent.signature_d = Math.pow(2, bytes.readUnsignedByte());
						bytes.position += 2;
						break;
					default:
						bytes.position += len;
						break;
					}
				}
				else if(temp == 0xF0 || temp == 0xF7)	//Sysx
				{
					event.type = temp;
					len = readVariableLength();
					event.sysx = new ByteArray();
					bytes.readBytes(event.sysx, 0, len);
				}
				else {
					if(temp & 0x80) {
						type = temp & 0xF0;
						channel = temp & 0x0F;
					}
					else {
						bytes.position--;
					}
					
					event.type = type;
					event.channel = channel;
					
					switch(type)
					{
					case 0x80:	//ノートオフ
						event.note = bytes.readUnsignedByte();
						event.velocity = bytes.readUnsignedByte();
						break;
					case 0x90:	//ノートオン
						event.note = bytes.readUnsignedByte();
						event.velocity = bytes.readUnsignedByte();
						break;
					case 0xA0:	//ポリフォニックキープレッシャー
						event.note = bytes.readUnsignedByte();
						event.value = bytes.readUnsignedByte();
						break;
					case 0xB0:	//コントロールチェンジ
						event.cc = bytes.readUnsignedByte();
						event.value = bytes.readUnsignedByte();
						break;
					case 0xC0:	//パッチチェンジ
						event.value = bytes.readUnsignedByte();
						break;
					case 0xD0:	//チャンネルプレッシャー
						event.value = bytes.readUnsignedByte();
						break;
					case 0xE0:	//ピッチベンド
						event.lsb = bytes.readUnsignedByte();
						event.msb = bytes.readUnsignedByte();
						break;
					}
				}
				sequence.push(event);
			}
			length = time;
		}
		
		public function toString():String
		{
			var text:String = length + "\n";
			
			for(var i:int = 0; i < sequence.length; i++)
			{
				if(sequence[i].toString() == "") {continue;}
				text += sequence[i].toString();
			}
			
			return text;
		}
	}

	dynamic class SMFEvent
	{
		public var delta_time:uint;	//相対時間
		public var time:uint;	//絶対時間
		public var type:int;
		
		public function toString():String
		{
			var text:String = "";
			//text = type.toString(16);
			//*
			switch(type)
			{
			case 0x90:
				if(this.velocity == 0) {break;}
				
				switch(this.note % 12)
				{
				case  0: text += "ド"; break;
				case  1: text += "ド#"; break;
				case  2: text += "レ"; break;
				case  3: text += "ミb"; break;
				case  4: text += "ミ"; break;
				case  5: text += "ファ"; break;
				case  6: text += "ファ#"; break;
				case  7: text += "ソ"; break;
				case  8: text += "ソ#"; break;
				case  9: text += "ラ"; break;
				case 10: text += "シb"; break;
				case 11: text += "シ"; break;
				}
				//text += " "+this.velocity;
				break;
			case 0xB0:
				text += "CC#" + this.cc +" "+this.value + " ";
				break;
			case 0xC0:
				text += "楽器変更 " + this.value + " ";
				break;
			case 0xF0:
			case 0xF7:
				text += "Sysx : ";
				for(var i:int = 0; i < this.sysx.length; i++) {
					text += this.sysx[i].toString(16)+" ";
				}
				break;
			}
			/*/
			if(type == 0x90 && this.velocity != 0)
			{
				switch(this.note % 12)
				{
				case  0: text += "c"; break;
				case  1: text += "c+"; break;
				case  2: text += "d"; break;
				case  3: text += "d+"; break;
				case  4: text += "e"; break;
				case  5: text += "f"; break;
				case  6: text += "f+"; break;
				case  7: text += "g"; break;
				case  8: text += "g+"; break;
				case  9: text += "a"; break;
				case 10: text += "a+"; break;
				case 11: text += "b"; break;
				}
			}
			//*/
			return text;
		}
	}
	
	class SMFBinary
	{
		public static const data:String = "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";
	}