Score Tracking
Track audio performance with your score(MIDI) file.
(The feature of accompaniment play is now under construction.)
1. Choose your Standard MIDI File you want to track with.
2. Prepare microphone to input audio performance with and allow microphone control.
3.
/**
* Copyright potato-attack ( http://wonderfl.net/user/potato-attack )
* MIT License ( http://www.opensource.org/licenses/mit-license.php )
* Downloaded from: http://wonderfl.net/c/hPty
*/
package
{
import adobe.utils.CustomActions;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.BlendMode;
import flash.display.Sprite;
import flash.display.StageAlign;
import flash.display.StageQuality;
import flash.display.StageScaleMode;
import flash.events.*;
import flash.events.SampleDataEvent;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.media.Microphone;
import flash.media.Sound;
import flash.media.SoundChannel;
import flash.utils.ByteArray;
import flash.utils.getTimer;
import flash.display.*;
import flash.text.*;
import flash.net.*;
// import flash.utils.ByteArray;
import mx.utils.Base64Decoder;
/**
* Real-time Sound Visualizer
*
* @author Kosuke Suzuki
*/
public class Main extends Sprite
{
//----------------------------------------
//CLASS CONSTANTS
private const ZEROS:Point = new Point();
//----------------------------------------
//VARIABLES
/**
* マイクロフォン
*/
private var _mic:Microphone;
/**
* 録音用
*/
private var _records:Vector.<Number>;
/**
* 再生用
*/
private var _sound:Sound;
private var _soundChannel:SoundChannel;
/**
* 録音、再生ヘッダ
*/
private var _position:int;
/**
* 描画用
*/
private var _canvas:BitmapData;
private var _slit:BitmapData;
private var _over:Sprite;
private var _canvas_under:BitmapData;
private var _pitch:PitchShifter;
private var _fftFrameSize:int = 4096;
private var samplingFreq:Number = 44100;
private var windowValues :Vector.<Number>;
private var _constantQfilterbank:FilterBank;
private var _channels:int = 60; // the number of pitch
private var _fl:Number = 50.0; // The lowest frequency
private var _Q:Number = 60.0; // Q value
private var shiftPosition:int = 2;
private var textfield:TextField = new TextField();
private var information:TextField = new TextField();
private var button:Sprite = new Sprite();
private var buttontext:TextField = new TextField();
private var fileReference:FileReference;
private var button2:Sprite = new Sprite();
private var button2text:TextField = new TextField();
private var midi:ScoreFollow;
//private var midiPlayer:MIDIPlayer;
//----------------------------------------
//STAGE INSTANCES
//----------------------------------------
//METHODS
/**
* Constructer
*/
public function Main():void
{
// prepare constant Q filterbank
_constantQfilterbank = new FilterBank(samplingFreq, _fftFrameSize, _Q, _fl, _channels);
//Wonderfl.disable_capture();
stage.align = StageAlign.TOP_LEFT;
stage.scaleMode = StageScaleMode.NO_SCALE;
stage.quality = StageQuality.LOW;
_mic = Microphone.getMicrophone();
_mic.rate = 44;
_mic.setSilenceLevel(0);
_mic.setUseEchoSuppression(true);
_sound = new Sound();
_canvas = new BitmapData(stage.stageWidth, stage.stageHeight/2, false, 0x0000ff);
//_slit = new BitmapData(1, stage.stageHeight/2, false, 0x000000);
addChild( new Bitmap(_canvas) );
//_over = addChild( new Sprite() ) as Sprite;
//_over.graphics.beginFill(0x0);
//_over.graphics.drawRect(0, 0, 100, 100);
//_over.graphics.endFill();
//
//_over.blendMode = BlendMode.INVERT; // invert the background color
//
_canvas_under = new BitmapData(stage.stageWidth, stage.stageHeight / 2, false, 0x000077);
var _canvas_under_bitmap:Bitmap = new Bitmap(_canvas_under);
_canvas_under_bitmap.y = stage.stageHeight / 2;
addChild(_canvas_under_bitmap);
windowValues = new Vector.<Number>(_fftFrameSize);
//windowValuesFactored = new Vector.<Number>(fftFrameSize);
//var invFftFrameSize2:Number = 2.0 / (fftFrameSize2 * osamp
var PI:Number = Math.PI;
var invFftFrameSizePI2:Number = PI * 2 / _fftFrameSize;
for (var k:int = 0, t:Number = 0.0; k < _fftFrameSize; ++k, t += invFftFrameSizePI2)
{
var window: Number = -.5 * Math.cos(t) + .5;
windowValues[k] = window;
//windowValuesFactored[k] = window * invFftFrameSize2;
}
_pitch = new PitchShifter(_fftFrameSize, 4, samplingFreq); // frame shift = 2048 / 4
button.x = 5;
button.y = 5;
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 = new TextField();
buttontext.width = 100;
buttontext.height = 20;
buttontext.htmlText = "<p align='center'><font face='_sans'>Open SMF</span></p>";
button.addChild(buttontext);
fileReference = new FileReference();
fileReference.addEventListener(Event.SELECT, onSelect);
fileReference.addEventListener(Event.COMPLETE, onComplete);
button.addEventListener(MouseEvent.CLICK, onClick);
button2.x = 200;
button2.y = 5;
button2.mouseChildren = false;
button2.buttonMode = true;
button2.graphics.lineStyle(1, 0xBBBBBB);
button2.graphics.beginFill(0xEEEEEE);
button2.graphics.drawRoundRect(0, 0, 200, 20, 5, 5);
button2.graphics.endFill();
addChild(button2);
button2text = new TextField();
button2text.width = 200;
button2text.height = 20;
button2text.htmlText = "<p align='center'><font face='_sans'>Load Accompaniment (disabled)</span></p>";
button2.addChild(button2text);
}
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
{
//読み込むファイルのアドレス情報を作成
var request:URLRequest = new URLRequest("http://dl.dropbox.com/u/13027309/Mitou/mean_ch.dat");
//URLLoaderのインスタンスを作成
var loader:URLLoader = new URLLoader();
//読み込み完了後の処理を作成
loader.addEventListener(Event.COMPLETE, txt2Vector);
//var loaderinfo:LoaderInfo = loader.con
var mean:Vector.<Number> = new Vector.<Number>();
function txt2Vector(event:Event):void
{
var testarray:Array = loader.data.split("\n");
var testdata:Vector.<Number> = new Vector.<Number>(12);
for (var i:int = 0; i < 12; i++)
{
testdata[i] = testarray[i];
}
//trace(testdata);
mean = testdata;
trace("Read Chromagram of Score COMPLETE!");
midi = new ScoreFollow(fileReference.data, _fftFrameSize, samplingFreq, mean,"http://dl.dropbox.com/u/13027309/Mitou/covmat_ch.dat");
midi.MakeChromagramOfScore_DP();
// Draw Chromagram Of Score
var bitmap_chromagram:BitmapData = DrawChromagramOfScore(midi.chromagram_sco, stage.stageWidth, stage.stageHeight / 2);
_canvas_under.copyPixels(bitmap_chromagram, bitmap_chromagram.rect, new Point(0,0));
_startRecord();
}
//読み込み失敗時の簡易処理を作成
loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR,trace);
loader.addEventListener(IOErrorEvent.IO_ERROR,trace);
//読み込みを開始
loader.load(request);
//trace(mean);
//_setTune();
}
// test function
private function _setTune():void
{
trace("test");
}
/**
* 録音開始
*/
private function _startRecord():void
{
trace("_startRecord");
// _over.width = 0;
_position = 0;
//_records = new Vector.<Number>(44100 * 5);
_records = new Vector.<Number>(_fftFrameSize);
_mic.addEventListener(SampleDataEvent.SAMPLE_DATA, _micSampleDataHandler);
}
private var gFFTworksp :Vector.<Number>; // FFT spectrum stack
private var _magn:Number, _real:Number, _imag:Number; // variables for FFT
private var _spectrum_temppower : Vector.<Number>;
private var _canvasPosition:int = 0; // Position on Canvas
private function _micSampleDataHandler(e:SampleDataEvent):void
{
var sw:int = stage.stageWidth;
var sh:int = stage.stageHeight;
var datas:ByteArray = e.data;
gFFTworksp = new Vector.<Number>(2 * _fftFrameSize + 2, true);
while (datas.bytesAvailable)
{
var data:Number = _records[_position] = datas.readFloat();
if (++_position == _records.length)
{
_canvasPosition = _canvasPosition + shiftPosition;
for (var k:int = 0, n:int = 1; k < _fftFrameSize; ++k, ++n)
{
gFFTworksp[n] = _records[k] * windowValues[k];
gFFTworksp[++n] = 0.0;
}
//delete _records;
_pitch.realft(gFFTworksp, _fftFrameSize, -1);
_position = 0;
_spectrum_temppower = new Vector.<Number> ( _fftFrameSize / 2 );
for (k = 0; k < _fftFrameSize/2; ++k)
{
/* de-interlace FFT buffer */
_real = gFFTworksp[n = 1 + (k << 1)];
_imag = gFFTworksp[n + 1];
/* compute magnitude and phase */
_magn = 2.0 * Math.sqrt(_real * _real + _imag * _imag);
_spectrum_temppower[k] = _magn;
}
// push spectrum to chromagram:ScoreFollow
var _tempchroma:Vector.<Number> = midi.spectrum2chroma(_spectrum_temppower, _constantQfilterbank);
midi.pushchroma(_tempchroma);
// tracking by Dynamic Time Warping
var _tempposition:Vector.<Number> = midi.tracking(_tempchroma);
// plot spectrogram
var x:int = _canvasPosition % sw;
//_canvas.copyPixels(_slit, _slit.rect, new Point(x + shiftPosition, 0));
//for (var y:Number = 0; y < _fftFrameSize / 2; ++y)
//{
//_canvas.setPixel(x, sh - y, Number( "0x" + _Number2RGB( Math.log(_spectrum_temppower[y]) / Math.LN10, 3.0, -1.5 ).toString(16) ) );
//}
var pixelchroma:BitmapData = DrawChroma(_tempchroma, shiftPosition, sh/2);
_canvas.copyPixels(pixelchroma, pixelchroma.rect, new Point(x, 0));
}
/*
var x:Number = _position / _records.length * sw;
_canvas.copyPixels(_slit, _slit.rect, new Point(x + 1, 0)); // erase previous wave at the next time
var y:Number = sh * 0.5 - data * 1000;
_canvas.setPixel(x, y, 0x00CC0000); // plot the point
*/
}
}
/**
* Spectrum to Color RGB
*/
//private function onLoadDemo(event:MouseEvent):void
//{
//var loader:URLLoader = new URLLoader();
//loader.addEventListener(Event.COMPLETE, onDemoLoaded);
//loader.dataFormat = URLLoaderDataFormat.BINARY;
//loader.load(new URLRequest("http://dl.dropbox.com/u/13027309/clarinet_quintet_1_all.mid"));
//}
//private function onDemoLoaded(event:Event):void
//{
//midi = new SMFSequence(event.currentTarget.data);
//textfield.text = midi.toString();
//}
/**
* 再生開始
*/
private function _startSound():void
{
trace("_startSound");
_position = 0;
_sound.addEventListener(SampleDataEvent.SAMPLE_DATA, _soundSampleDataHandler);
_soundChannel = _sound.play();
}
private function _soundSampleDataHandler(e:SampleDataEvent):void
{
_over.width = stage.stageWidth * (_position / _records.length);
_over.height = stage.stageHeight;
for (var i:int = 0; i < 2048; ++i)
{
var data:Number = _records[_position];
e.data.writeFloat(data);
e.data.writeFloat(data);
if (++_position == _records.length)
{
_sound.removeEventListener(SampleDataEvent.SAMPLE_DATA, _soundSampleDataHandler);
_startRecord();
return;
}
}
}
private function DrawChroma(chroma:Vector.<Number>, width:int, height:int):BitmapData
{
//var width:int = height / 12;
var bitmap_chroma:BitmapData = new BitmapData(width, height);
var stream:ByteArray = new ByteArray();
var unit:int = Math.floor(height / 12);
var rect:Rectangle = new Rectangle(0, 0, width, unit*12);
var i:int = 0;
for (var index:int = 0; index < 12; index++)
{
var rgb:uint = _Number2RGB(chroma[index], 1.0, 0.0);
while (i < 4 * width * unit * (index+1))
{
var alpha:uint = 255;
var red :uint = Math.floor(rgb / 16 / 16 / 16 / 16);
var green:uint = Math.floor((rgb - red * 16 * 16 * 16 * 16) / 16 / 16);
var blue :uint = rgb - red * 16 * 16 * 16 * 16 - green * 16 * 16;
stream[i++] = alpha;
stream[i++] = red;
stream[i++] = green;
stream[i++] = blue;
}
}
bitmap_chroma.setPixels(rect, stream);
//for (var y:int = 0; y < 12; y++)
//{
//bitmap_chroma.fillRect(new Rectangle(0, unit*y, width, unit), Number( "0x" + _Number2RGB(chroma[y], 1, 0 ).toString(16) ) );
//for (var x:int = 0; x < width; x++)
//{
//bitmap_chroma.setPixel(x, y, Number( "0x" + _Number2RGB(chroma[y], 1, 0 ).toString(16) ) );
//}
//}
return bitmap_chroma;
}
private function DrawChromagramOfScore(chromagram:Vector.<Vector.<Number>>, width:int, height:int):BitmapData
{
var bitmap_chromagram:BitmapData = new BitmapData(width, height);
for (var index:int = 0; index < chromagram.length; index++)
{
var bitmap_chroma:BitmapData = DrawChroma(chromagram[index], shiftPosition, height);
bitmap_chromagram.copyPixels(bitmap_chroma, bitmap_chroma.rect, new Point(shiftPosition * index, 0));
}
return bitmap_chromagram;
}
private function DrawLineOfPosition(position:int):void
{
}
}
}
class FilterBank
{
private var filter:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
private var filter_real:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
private var filter_imag:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
public var channels:Number;
public function FilterBank(samplingFreq:Number, fftFrameSize:Number, Q:Number, lowestFreq:Number, channels:Number):void
{
this.channels = channels;
var fc:Number;
// prepare constant-Q filterbank
for (var m:int = 0; m < channels; ++m)
{
filter.push(new Vector.<Number>(fftFrameSize/2));
filter_real.push(new Vector.<Number>(fftFrameSize/2));
filter_imag.push(new Vector.<Number>(fftFrameSize/2));
for (var n:int = 0; n< fftFrameSize / 2; ++n)
{
fc = lowestFreq * Math.pow(2, (m-1)/12);
filter[m][n] = Math.exp( - (n - fc) * (n - fc) * Q * Q / 2 / fc / fc );
// phase (by tachibana) but dubious ???
filter_imag[m][n] = filter[m][n] * Math.sin(Math.PI * (fc - n) * fftFrameSize / samplingFreq);
filter_real[m][n] = filter[m][n] * Math.cos(Math.PI * (fc - n) * fftFrameSize / samplingFreq);
}
}
}
public function readfilter():Vector.<Vector.<Number>>
{
return filter;
}
public function readfilter_real():Vector.<Vector.<Number>>
{
return filter_real;
}
public function readfilter_imag():Vector.<Vector.<Number>>
{
return filter_imag;
}
}
class ScoreFollow
{
private var fftFrameSize:Number;
private var samplingFreq:Number;
private var chromagram_per:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
public var chromagram_sco:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
private var trellis:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
private var clarinet:ChromaTemplate;
private var score:SMFSequence;
public function ScoreFollow(data:ByteArray, fftFrameSize:Number, samplingFreq:Number, mean:Vector.<Number>, covmat:String):void
{
this.score = new SMFSequence(data);
this.fftFrameSize = fftFrameSize;
this.samplingFreq = samplingFreq;
//spectrogram ;
trace(this.score.tracks.toString());
clarinet = new ChromaTemplate(mean, covmat, this);
//clarinet = new ChromaTemplate("C:\Users\Kosuke\ Suzuki\Dropbox\Public\Mitou\mean_ch.dat",
//"C:\Users\Kosuke\ Suzuki\Dropbox\Public\Mitoucovmat_ch.dat");
// Make Chromagram of Score for Tracking
//while (clarinet.finish == false)
//{
//
//}
//MakeChromagramOfScore_DP();
}
public function MakeChromagramOfScore_DP():void
{
var time_unit:Number = fftFrameSize / samplingFreq;
var frame:int = 0; // frame index : frame * time_unit = sec
var index:int = 0; // MIDI event index
var ListNoteOn:Vector.<SMFEvent> = new Vector.<SMFEvent>();
var listNoteOnEvent:Function = function(smfevent:SMFEvent, index:int, smftrack:Vector.<SMFEvent>):Boolean
{
if (smfevent.type == 0x90)
{
return true;
}
return false;
};
ListNoteOn = score.tracks[1].sequence.filter(listNoteOnEvent);
var previous_onset:int = 0; // Onset time of previous event
var lastindex:int = ListNoteOn.length-1;
//trace(score.tracks[1].sequence[lastindex].time);
var lastframe:int = ListNoteOn[lastindex].time * 60 / score.tempo / score.division / time_unit;
for (frame = 0; frame < lastframe; frame++)
{
var tempchroma:Vector.<Number> = new Vector.<Number>();
tempchroma = clarinet.rotate_mean(ListNoteOn[index].note % 12);
// Normalization
var zero:Vector.<Number> = new <Number>[0,0,0,0,0,0,0,0,0,0,0,0];
var norm:Number = distance(tempchroma, zero);
for (var k:int = 0; k < 12; k++)
{
tempchroma[k] /= norm + 0.001;
}
chromagram_sco.push(tempchroma);
if ( frame * time_unit > ListNoteOn[index].time * 60 / score.tempo / score.division )
{
index++;
}
}
//trace(chromagram_sco)
}
public function spectrum2chroma(power_spectrum:Vector.<Number>, filter:FilterBank):Vector.<Number>
{
var wavelet:Vector.<Number> = new Vector.<Number>(filter.channels);
var chroma :Vector.<Number> = new Vector.<Number>(12);
for (var m:int = 0; m < filter.channels; m++)
{
for (var n:int = 0; n < fftFrameSize / 2; n++)
{
wavelet[m] += filter.readfilter()[m][n] * power_spectrum[n];
}
}
for (m = 0; m < filter.channels; m++)
{
chroma[m % 12] += wavelet[m];
}
// Normalization
var zero:Vector.<Number> = new <Number>[0,0,0,0,0,0,0,0,0,0,0,0];
var norm:Number = distance(chroma, zero);
for (var k:int = 0; k < 12; k++)
{
chroma[k] /= norm + 0.001;
}
return chroma;
}
public function pushchroma(chroma:Vector.<Number>):void
{
chromagram_per.push(chroma);
//trace(spectrogram);
}
public function tracking(tempchroma:Vector.<Number>):Vector.<Number>
{
var index:int = chromagram_per.length - 1;
var temptrellis:Vector.<Number> = new Vector.<Number>(chromagram_sco.length);
for (var k:int = 0; k < chromagram_sco.length; k++)
{
temptrellis[k] = distance(tempchroma, chromagram_sco[k]);
trellis.push(temptrellis);
}
if (index == 0)
{
for (k=1; k < chromagram_sco.length; k++)
{
trellis[index][k] += trellis[index][k - 1];
}
}
else
{
trellis[index][0] += trellis[index - 1][0];// k=0
for (k=1; k < chromagram_sco.length; k++) // k=[1:N]
{
trellis[index][k] = Math.min(trellis[index][k] + trellis[index][k - 1]
,trellis[index][k] + trellis[index - 1][k]
,trellis[index][k] + trellis[index - 1][k - 1]);
}
}
return seakmin(trellis[index]);
}
public function seakmin(v:Vector.<Number>):Vector.<Number>
{
var answer:Vector.<Number> = new <Number> [v[0],0];// answer[0] is minimal value and answer[1] is its index
if (v.length != 1)
{
for (var i:int = 1; i < v.length; i++)
{
if (v[i] < answer[0])
{
answer[1] = i;
answer[0] = v[i];
}
}
}
return answer;
}
public function distance(v1:Vector.<Number>, v2:Vector.<Number>):Number
{
if (v1.length != v2.length)
{
return -1;
}
var length:int = v1.length;
var distance:Number = 0;
for (var i:int = 0; i < length; i++)
{
distance += (v1[i] - v2[i]) * (v1[i] - v2[i]);
}
return Math.sqrt(distance);
}
}
class ChromaTemplate
{
public var mean :Vector.<Number> = new Vector.<Number>(12);
public var covmat:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
private var chroma_of_score : Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
public function ChromaTemplate(mean:Vector.<Number>, covmatfile:String, scorefollow:ScoreFollow)
{
this.mean = mean;
//読み込むファイルのアドレス情報を作成
//var request_cov:URLRequest = new URLRequest(covmatfile);
//URLLoaderのインスタンスを作成
//var loader_cov:URLLoader = new URLLoader();
//読み込み完了後の処理を作成
//loader_cov.addEventListener(Event.COMPLETE,traceData_cov);
//
//function traceData_cov(event:Event):void {
//var testarray:Array = loader_cov.data.split("\n");
//var covdata:Vector.<Vector.<Number>> = new Vector.<Vector.<Number>>();
//
//for (var j:int = 0; j < 12; j++)
//{
//var temparray:Array = testarray[j].split(" ");
//var tempdata:Vector.<Number> = new Vector.<Number>(12);
//for (var i:int = 0; i < 12; i++)
//{
//tempdata[i] = temparray[i];
//}
//
//covdata[j] = tempdata;
//}
//
//trace(covdata[2]);
//
//this.covmat = covdata;
//}
//
//読み込み失敗時の簡易処理を作成
//loader_cov.addEventListener(SecurityErrorEvent.SECURITY_ERROR,trace);
//loader_cov.addEventListener(IOErrorEvent.IO_ERROR,trace);
//
//読み込みを開始
//loader_cov.load(request_cov);
}
public function rotate_mean(shift:int):Vector.<Number>
{
var rotated_mean:Vector.<Number> = new Vector.<Number>(12);
for (var i:int = 0; i < 12-shift; i++)
{
rotated_mean[i + shift] = this.mean[i];
}
for (var j:int = 0; j < shift; j++)
{
rotated_mean[j] = this.mean[12 - shift + j];
}
return rotated_mean;
}
}
import flash.utils.ByteArray;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.events.*;
/*
* class Standard MIDI File
*/
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();
trace("division is ", division);
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;
}
//trace("tempo is ", parent.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();
text += sequence[i].time * 60 / parent.tempo / parent.division ;
text += " ";
}
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: // Note Event (?)
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;
}
}
/****************************************************************************
*
* NAME: PitchShifter.as
* VERSION: 1.0
* HOME URL: http://iq12.com/
* KNOWN BUGS: none
*
* SYNOPSIS: Routine for doing pitch shifting while maintaining
* duration using the Short Time Fourier Transform.
*
* DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5
* (one octave down) and 2. (one octave up). A value of exactly 1 does not change
* the pitch. numSampsToProcess tells the routine how many samples in indata[0...
* numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ...
* numSampsToProcess-1]. The two buffers can be identical (ie. it can process the
* data in-place). fftFrameSize defines the FFT frame size used for the
* processing. Typical values are 1024, 2048 and 4096. It may be any value <=
* MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT
* oversampling factor which also determines the overlap between adjacent STFT
* frames. It should at least be 4 for moderate scaling ratios. A value of 32 is
* recommended for best quality. sampleRate takes the sample rate for the signal
* in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in
* indata[] should be in the range [-1.0, 1.0), which is also the output range
* for the data, make sure you scale the data accordingly (for 16bit signed integers
* you would have to divide (and multiply) by 32768).
*
* COPYRIGHT 1999-2006 Stephan M. Bernsee <smb [AT] dspdimension [DOT] com>
*
* The Wide Open License (WOL)
*
* Permission to use, copy, modify, distribute and sell this software and its
* documentation for any purpose is hereby granted without fee, provided that
* the above copyright notice and this license appear in all source copies.
* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF
* ANY KIND. See http://www.dspguru.com/wol.htm for more information.
*
*****************************************************************************/
/****************************************************************************
*
* This code was converted to AS3/FP10 by Arnaud Gatouillat <fu [AT] iq12 [DOT] com>
* from C# code by Michael Knight ( madmik3 at gmail dot com. )
* http://sites.google.com/site/mikescoderama/
*
*****************************************************************************/
/****************************************************************************
*
* The functions `realft' and `four1' are based on those in Press, W.H., et al.,
* Numerical Recipes in C: the Art of Scientific Computing (Cambridge Univ. Press,
* 1989; 2nd ed., 1992).
*
*****************************************************************************/
class PitchShifter
{
private var gInFIFO :Vector.<Number>;
private var gOutFIFO :Vector.<Number>;
private var gFFTworksp :Vector.<Number>;
private var gLastPhase :Vector.<Number>;
private var gSumPhase :Vector.<Number>;
private var gOutputAccum:Vector.<Number>;
private var gAnaFreq :Vector.<Number>;
private var gAnaMagn :Vector.<Number>;
private var gSynFreq :Vector.<Number>;
private var gSynMagn :Vector.<Number>;
private var freqPerBin:Number, expct:Number;
private var gRover:int, inFifoLatency:int, stepSize:int, fftFrameSize2:int;
private var fftFrameSize:int, osamp:int, sampleRate:Number;
/* pre-computed values for speed */
private var windowValues :Vector.<Number>;
private var windowValuesFactored:Vector.<Number>;
private var invPI:Number, invFftFrameSizePI2:Number, osampPI2:Number, invOsampPI2FreqBin:Number;
private var PI:Number = Math.PI
private var TWOPI:Number = 2 * Math.PI
public function PitchShifter(fftFrameSize:int, osamp:int, sampleRate:Number)
{
this.fftFrameSize = fftFrameSize;
this.osamp = osamp;
this.sampleRate = sampleRate;
gInFIFO = new Vector.<Number>(fftFrameSize);
gOutFIFO = new Vector.<Number>(fftFrameSize, true);
gFFTworksp = new Vector.<Number>(2 * fftFrameSize + 2, true);
gLastPhase = new Vector.<Number>(fftFrameSize / 2 + 1, true);
gSumPhase = new Vector.<Number>(fftFrameSize / 2 + 1, true);
gOutputAccum = new Vector.<Number>(2 * fftFrameSize, true);
gAnaFreq = new Vector.<Number>(fftFrameSize, true);
gAnaMagn = new Vector.<Number>(fftFrameSize, true);
gSynFreq = new Vector.<Number>(fftFrameSize, true);
gSynMagn = new Vector.<Number>(fftFrameSize, true);
/* set up some handy variables */
fftFrameSize2= fftFrameSize / 2;
stepSize = fftFrameSize / osamp;
freqPerBin = sampleRate / Number(fftFrameSize);
expct = 2.0 * PI * Number(stepSize) / Number(fftFrameSize);
inFifoLatency = fftFrameSize - stepSize;
invPI = 1 / PI;
invFftFrameSizePI2 = PI * 2 / fftFrameSize;
osampPI2 = osamp / ( 2 * PI );
invOsampPI2FreqBin = 1 / ( freqPerBin * osampPI2);
windowValues = new Vector.<Number>(fftFrameSize);
windowValuesFactored = new Vector.<Number>(fftFrameSize);
var invFftFrameSize2:Number = 2.0 / (fftFrameSize2 * osamp);
for (var k:int = 0, t:Number = 0.0; k < fftFrameSize; ++k, t += invFftFrameSizePI2)
{
var window: Number = -.5 * Math.cos(t) + .5;
windowValues[k] = window;
windowValuesFactored[k] = window * invFftFrameSize2;
}
}
public function pitchShift(pitchShift:Number, numSampsToProcess:int, indata:Vector.<Number>):void
{
var magn:Number, phase:Number, tmp:Number, window:Number, real:Number, imag:Number, t:Number;
var i:int, k:int, qpd:int, index:int, n:int;
var outdata:Vector.<Number> = indata;
if (gRover == 0) gRover = inFifoLatency;
/* main processing loop */
for (i = 0; i < numSampsToProcess; ++i)
{
/* As long as we have not yet collected enough data just read in */
gInFIFO[gRover] = indata[i];
outdata[i] = gOutFIFO[gRover - inFifoLatency];
++gRover;
/* now we have enough data for processing */
if (gRover >= fftFrameSize)
{
gRover = inFifoLatency;
/* do windowing and re,im interleave */
for (k = 0, n = 1; k < fftFrameSize; ++k, ++n)
{
gFFTworksp[n] = gInFIFO[k] * windowValues[k];
gFFTworksp[++n] = 0.0;
}
/* ***************** ANALYSIS ******************* */
/* do transform */
realft(gFFTworksp, fftFrameSize, -1);
/* this is the analysis step */
for (k = 0; k <= fftFrameSize2; ++k)
{
/* de-interlace FFT buffer */
real = gFFTworksp[n = 1 + (k << 1)];
imag = gFFTworksp[n + 1];
/* compute magnitude and phase */
magn = 2.0 * Math.sqrt(real * real + imag * imag);
phase = Math.atan2(imag, real);
/* compute phase difference */
tmp = phase - gLastPhase[k];
gLastPhase[k] = phase;
/* subtract expected phase difference */
tmp -= k * expct;
/* map delta phase into +/- Pi interval */
qpd = int(tmp * invPI);
if (qpd >= 0) qpd += qpd & 1;
else qpd -= qpd & 1;
tmp -= PI * Number(qpd);
/* get deviation from bin frequency from the +/- Pi interval */
tmp *= osampPI2;
/* compute the k-th partials' true frequency */
tmp = (k + tmp) * freqPerBin;
/* store magnitude and true frequency in analysis arrays */
gAnaMagn[k] = magn;
gAnaFreq[k] = tmp;
}
/* ***************** PROCESSING ******************* */
/* this does the actual pitch shifting */
for (var zero:int = 0; zero < fftFrameSize; ++zero)
{
gSynMagn[zero] = 0.0;
gSynFreq[zero] = 0.0;
}
for (k = 0, n = pitchShift > 1.0 ? int(fftFrameSize2 / pitchShift) : fftFrameSize2; k <= n; ++k)
{
index = int(k * pitchShift);
gSynMagn[index] += gAnaMagn[k];
gSynFreq[index] = gAnaFreq[k] * pitchShift;
}
/* ***************** SYNTHESIS ******************* */
/* this is the synthesis step */
for (k = 0; k <= fftFrameSize2; ++k)
{
/* get magnitude and true frequency from synthesis arrays */
magn = gSynMagn[k];
/* subtract bin mid frequency */
/* get bin deviation from freq deviation */
/* take osamp into account */
/* add the overlap phase advance back in */
/* accumulate delta phase to get bin phase */
phase = (gSumPhase[k] += (gSynFreq[k] - Number(k) * freqPerBin) * invOsampPI2FreqBin + Number(k) * expct);
/* get real and imag part and re-interleave */
gFFTworksp[n = 1 + (k << 1)] = magn * Math.cos(phase);
gFFTworksp[n + 1] = magn * Math.sin(phase);
}
/* zero negative frequencies */
for (k = fftFrameSize + 3, n = 1 + (fftFrameSize << 1); k < n; ++k)
{
gFFTworksp[k] = 0.0;
}
/* do inverse transform */
realft(gFFTworksp, fftFrameSize, 1);
/* do windowing and add to output accumulator */
for (k = 0, n = 1; k < fftFrameSize; ++k, ++n, ++n)
{
gOutputAccum[k] += windowValuesFactored[k] * gFFTworksp[n];
}
for (k = 0; k < stepSize; ++k)
{
gOutFIFO[k] = gOutputAccum[k];
}
//memmove(gOutputAccum, gOutputAccum + stepSize, fftFrameSize * sizeof(Number));
/* shift accumulator */
/* move input FIFO */
for (k = 0, n = stepSize; k < inFifoLatency; ++k, ++n)
{
gOutputAccum[k] = gOutputAccum[n];
gInFIFO[k] = gInFIFO[n];
}
for ( ; k < fftFrameSize; ++k, ++n)
{
gOutputAccum[k] = gOutputAccum[n];
}
}
}
}
//private function realft( data:Vector.<Number>, n:int, isign:int ):void
public function realft( data:Vector.<Number>, n:int, isign:int ):void
{
var i:int, i1:int, i2:int, i3:int, i4:int, n2p3:int;
var c1:Number = 0.5, c2:Number, h1r:Number, h1i:Number, h2r:Number, h2i:Number;
var wr:Number, wi:Number, wpr:Number, wpi:Number, wtemp:Number, theta:Number;
theta = PI/n;
if (isign == 1)
{
c2 = -0.5;
four1(data, n, 1);
}
else
{
c2 = 0.5;
theta = -theta;
}
wtemp = Math.sin(0.5 * theta);
wpr = -2.0 * wtemp * wtemp;
wpi = Math.sin(theta);
wr = 1.0 + wpr;
wi = wpi;
n2p3 = 2 * n + 3;
for (i = 2; i <= n / 2; ++i)
{
i4 = 1 + (i3 = n2p3 - (i2 = 1 + ( i1 = i + i - 1)));
h1r = c1 * (data[i1] + data[i3]);
h1i = c1 * (data[i2] - data[i4]);
h2r = -c2 * (data[i2] + data[i4]);
h2i = c2 * (data[i1] - data[i3]);
data[i1] = h1r + wr * h2r - wi * h2i;
data[i2] = h1i + wr * h2i + wi * h2r;
data[i3] = h1r - wr * h2r + wi * h2i;
data[i4] = -h1i + wr * h2i + wi * h2r;
wr = (wtemp = wr) * wpr - wi * wpi + wr;
wi = wi * wpr + wtemp * wpi + wi;
}
if (isign == 1)
{
data[1] = (h1r = data[1]) + data[2];
data[2] = h1r - data[2];
}
else
{
data[1] = c1 * ((h1r = data[1]) + data[2]);
data[2] = c1 * (h1r - data[2]);
four1(data, n, -1);
data=data;
}
}
private function four1(data:Vector.<Number>, nn:int, isign:int):void
{
var n:int, mmax:int, m:int, j:int, istep:int, i:int;
var wtemp:Number, wr:Number, wpr:Number, wpi:Number, wi:Number, theta:Number;
var tempr:Number, tempi:Number;
var j1:int, i1:int;
n = nn << 1;
j = 1;
for (i = 1; i < n; i += 2)
{
if (j > i)
{
j1 = j + 1;
i1 = i + 1;
tempr = data[j]; data[j] = data[i]; data[i] = tempr;
tempr = data[j1]; data[j1] = data[i1]; data[i1] = tempr;
}
m = n >> 1;
while (m >= 2 && j > m)
{
j -= m;
m >>= 1;
}
j += m;
}
mmax = 2;
while (n > mmax)
{
istep = 2 * mmax;
theta = TWOPI / (isign * mmax);
wtemp = Math.sin(0.5 * theta);
wpr = -2.0 * wtemp * wtemp;
wpi = Math.sin(theta);
wr = 1.0;
wi = 0.0;
for (m = 1; m < mmax; m += 2)
{
for (i = m; i <= n; i += istep)
{
i1 = i +1;
j1 = 1+ (j = i + mmax);
tempr = wr*data[j] - wi*data[j1];
tempi = wr*data[j1] + wi*data[j];
data[j] = data[i] - tempr;
data[j1] = data[i1] - tempi;
data[i] += tempr;
data[i1] += tempi;
}
wr = (wtemp = wr) * wpr - wi * wpi + wr;
wi = wi * wpr + wtemp * wpi + wi;
}
mmax = istep;
}
}
}
function _Number2RGB(_value:Number, max_value:Number, min_value:Number):uint
{
if (_value > max_value)
{
_value = max_value;
}
else if (_value < min_value)
{
_value = min_value;
}
_value = (_value - min_value) / (max_value - min_value);
var h:Number = 240.0 * ( 1 - _value % 1 ) ; // normalize between 0 and 240
var s:Number = 1.0;
var v:Number = 1.0;
var rgb:uint = 0;
var hi:uint = Math.floor(h / 60.0) % 6;
var f:Number = h / 60.0 - hi;
var vv:uint = Math.round(255 * v);
var pp:uint = Math.round(255 * v * ( 1 - s ));
var qq:uint = Math.round(255 * v * ( 1 - f * s ));
var tt:uint = Math.round(255 * v * ( 1 - (1 - f) * s ));
if ( vv > 255 ) vv = 255;
if ( pp > 255 ) pp = 255;
if ( qq > 255 ) qq = 255;
if ( tt > 255 ) tt = 255;
switch (hi) {
case 0: rgb = (vv << 16) | (tt << 8) | pp; break;
case 1: rgb = (qq << 16) | (vv << 8) | pp; break;
case 2: rgb = (pp << 16) | (vv << 8) | tt; break;
case 3: rgb = (pp << 16) | (qq << 8) | vv; break;
case 4: rgb = (tt << 16) | (pp << 8) | vv; break;
case 5: rgb = (vv << 16) | (pp << 8) | qq; break;
}
// var _RGBst:uint = Number("0x" + temp_value.toString(16));
//return _RGBst;
return rgb;
}
import flash.utils.*;
function wait(count:uint ):void
{
var start:uint = getTimer();
while (getTimer() - start < count)
{
}
}