Score Tracking
Track audio performance with your score (MIDI) file.
/**
* 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/oUmB
*/
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 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 + 1;
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
midi.tracking(_tempchroma);
// plot spectrogram
var x:int = _canvasPosition % sw;
_canvas.copyPixels(_slit, _slit.rect, new Point(x + 1, 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, 1, stage.stageHeight/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], 1, height);
bitmap_chromagram.copyPixels(bitmap_chroma, bitmap_chroma.rect, new Point(index,0));
}
return bitmap_chromagram;
}
}
}
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>):void
{
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]);
}
}
trace(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)
{
}
}