In case Flash no longer exists; a copy of this site is included in the Flashpoint archive's "ultimate" collection.

Dead Code Preservation :: Archived AS3 works from wonderfl.net

8Bitboy Mod Playerを作ってみましたが不具合?

今度は8bitboy mod playerを作ってみましたが
* 再生してみるとプツプツとスムーズに再生されません。
* 前回は一気に変換していましたが、今回は少しずつ
* 変換再生しているので処理と同期に問題があるのかな?
* デバックしてくれる人を求め、とりあえずアップしました。
* http://8bitboy.popforge.de/
Get Adobe Flash player
by TX_298 26 May 2009
/**
* 今度は8bitboy mod playerを作ってみましたが
* 再生してみるとプツプツとスムーズに再生されません。
* 前回は一気に変換していましたが、今回は少しずつ
* 変換再生しているので処理と同期に問題があるのかな?
* デバックしてくれる人を求め、とりあえずアップしました。
* http://8bitboy.popforge.de/ 
*/
package 
{
	import flash.display.Sprite;
	import flash.utils.ByteArray;
        [SWF(width = "465", height = "465", frameRate = "30", backgroundColor = "0xffffff")]
	public class BitboyTestApp extends Sprite
	{
		private var bitboy: BitBoy;
		private	var	buffer:AudioBuffer;
		private	var format: FormatBase;
		
		public function BitboyTestApp()
		{
			init();
		}
		
		private function init(): void
		{			
			bitboy = new BitBoy();
			
			buffer = new AudioBuffer( 4, Audio.STEREO, Audio.BIT16, Audio.RATE44100 );
			buffer.onInit = onAudioBufferInit;
			buffer.onComplete = onAudioBufferComplete;
		}
	
		private function onAudioBufferInit( buffer: AudioBuffer ): void
		{			
			format = FormatFactory.createFormat( new Base64(Mod.Snd) );
			bitboy.setFormat( format );
		//	bitboy.parameterPause.setValue( !true );
			if( !buffer.isPlaying() ){buffer.start();	}
		}

		private function onAudioBufferComplete( buffer: AudioBuffer ): void
		{
			var samples: Array = buffer.getSamples();
			bitboy.processAudio( samples );
			buffer.update();
		}
	}
}
	import flash.utils.ByteArray;

	class XMInstrumentHeader
	{
		public var size: uint;
		public var name: String;
		public var type: uint;
		public var numSamples: uint;
		
		public function XMInstrumentHeader( stream: ByteArray )
		{
			parse( stream );
		}
		
		private function parse( stream: ByteArray ): void
		{
			size = stream.readUnsignedInt();
			name = stream.readMultiByte( 22, XMFormat.ENCODING );
			type = stream.readUnsignedByte();
			numSamples = stream.readUnsignedShort();
		}
		
		public function toString(): String
		{
			return '[XMInstrumentHeader size: ' + size + ', name: ' + name + ', type: ' + type.toString(2) + ', numSamples: ' + numSamples + ']';
		}
	}
	class XMVolumeCommand
	{
		public static const NO_COMMAND: uint = 0x00;
		public static const VOLUME: uint = 0x01;
		
		public static const VOLUME_SLIDE_DOWN: uint = 0x60;
		public static const VOLUME_SLIDE_UP: uint = 0x70;
		public static const VOLUME_FINE_DOWN: uint = 0x80;
		public static const VOLUME_FINE_UP: uint = 0x90;
		public static const VIBRATO_SPEED: uint = 0xa0;
		public static const VIBRATO: uint = 0xb0;
		public static const PANNING: uint = 0xc0;
		public static const PANNING_SLIDE_LEFT: uint = 0xd0;
		public static const PANNING_SLIDE_RIGHT: uint = 0xe0;
		public static const TONE_PORTAMENTO: uint = 0xf0;
	}
	
	import flash.geom.Point;
	import flash.utils.ByteArray;
	
	class XMSampleHeader
	{
		public var size: uint;
		
		public var sampleNumber: Array;
		
		public var volumeEnvelope: Array;
		public var panningEnvelope: Array;
		
		public var numVolumePoints: uint;
		public var numPanningPoints: uint;
		
		public var volumeSustainPoint: uint;
		public var volumeLoopStartPoint: uint;
		public var volumeLoopEndPoint: uint;
			
		public var panningSustainPoint: uint;
		public var panningLoopStartPoint: uint;
		public var panningLoopEndPoint: uint;
		
		public var volumeType: uint;
		public var panningType: uint;
			
		public var vibratoType: uint;
		public var vibratoSweep: uint;
		public var vibratoDepth: uint;
		public var vibratoRate: uint;
			
		public var volumeFadeOut: uint;
		
		public function XMSampleHeader( stream: ByteArray )
		{
			parse( stream );
		}
		
		private function parse( stream: ByteArray ): void
		{
			var i: int;
			var x: int;
			var y: int;
			
			size = stream.readUnsignedInt();
			sampleNumber = new Array( 96 );
			
			for ( i = 0; i < 96; ++i )
			{
				sampleNumber[ i ] = stream.readUnsignedByte();
			}
			
			volumeEnvelope = new Array( 12 );
			
			for ( i = 0; i < 12; ++i )
			{
				x = stream.readUnsignedShort();
				y = stream.readUnsignedShort();
				
				volumeEnvelope[ i ] = new Point( x, y );
			}
			
			panningEnvelope = new Array( 12 );
			
			for ( i = 0; i < 12; ++i )
			{
				x = stream.readUnsignedShort();
				y = stream.readUnsignedShort();
				
				panningEnvelope[ i ] = new Point( x, y );
			}
			
			numVolumePoints = stream.readUnsignedByte();
			
			if ( numVolumePoints > 12 )
			{
				trace( 'Waning: numVolumePoints is greater than 12 which should be the maximum.' );
				numVolumePoints = 12;
			}
			
			numPanningPoints = stream.readUnsignedByte();
			
			if ( numPanningPoints > 12 )
			{
				trace( 'Waning: numPanningPoints is greater than 12 which should be the maximum.' );
				numPanningPoints = 12;
			}
			
			volumeSustainPoint = stream.readUnsignedByte();
			volumeLoopStartPoint = stream.readUnsignedByte();
			volumeLoopEndPoint = stream.readUnsignedByte();
			
			panningSustainPoint = stream.readUnsignedByte();
			panningLoopStartPoint = stream.readUnsignedByte();
			panningLoopEndPoint = stream.readUnsignedByte();
			
			//TODO: implement this bitflag
			// Volume type: bit 0: On; 1: Sustain; 2: Loop
			volumeType = stream.readUnsignedByte();

			//TODO: implement this bitflag
			// Panning type: bit 0: On; 1: Sustain; 2: Loop
			panningType = stream.readUnsignedByte();
		
			vibratoType = stream.readUnsignedByte();
			vibratoSweep = stream.readUnsignedByte();
			vibratoDepth = stream.readUnsignedByte();
			vibratoRate = stream.readUnsignedByte();
			
			volumeFadeOut = stream.readUnsignedShort();
			
			//-- unused
			stream.readMultiByte( 11, XMFormat.ENCODING );
		}
		
		public function toString(): String
		{
			const ENUM: Array = ['size',
				'sampleNumber',
				'volumeEnvelope',
				'panningEnvelope',
				'numVolumePoints',
				'numPanningPoints',
				'volumeSustainPoint',
				'volumeLoopStartPoint',
				'volumeLoopEndPoint',
				'panningSustainPoint',
				'panningLoopStartPoint',
				'panningLoopEndPoint',
				'volumeType',
				'panningType',
				'vibratoType',
				'vibratoSweep',
				'vibratoDepth',
				'vibratoRate',
				'volumeFadeOut'];
			
			var result: String = '[XMSampleHeader';
			
			for ( var i: int = 0; i < ENUM.length; i++ )
			{
				result += ( i == 0 ? ' ' : ', ' ) + ENUM[ i ] + ': ' + this[ENUM[i]];
			}
			
			return result + ']';
		}
	}
	
	interface IMapping
	{
		function map( normalizedValue: Number ): *;
		function mapInverse( value: * ): Number;
	}
	
	import flash.utils.IDataInput;
	import flash.utils.IDataOutput;
	import flash.utils.IExternalizable;
	import flash.net.registerClassAlias;
	
	/**
	 * class Parameter stores an untyped value
	 * Depending on its mapping it can handle different types of values
	 * as Number, Boolean, Array
	 * 
	 * It also informs listeners if the value has changed.
	 * 
	 * @author Andre Michelle
	 */
	class Parameter
		implements IExternalizable
	{
		{
			registerClassAlias( 'Parameter', Parameter );
		}
		
		private var value: *;
		private var mapping: IMapping;

		private var defaultValue: *;

		private var changedCallbacks: Array;
		
		/**
		 * Creates a Parameter instance
		 * 
		 * @param mapping The mapping used to map/mapInverse the normalized value
		 * @param value The default values
		 */
		public function Parameter( mapping: IMapping = null, value: * = null )
		{
			this.mapping = mapping;
			this.value = defaultValue = value;
			
			changedCallbacks = new Array();
		}
		
		public function writeExternal( output: IDataOutput ): void
		{
			output.writeObject( value );
			output.writeObject( defaultValue );
		}
		
		public function readExternal( input: IDataInput ): void
		{
			setValue( input.readObject() );
			defaultValue = input.readObject();
		}

		/**
		 * Sets the current value of the parameter
		 * 
		 * if changed, inform all callbacks
		 */
		public function setValue( value: * ): void
		{
			var oldValue: * = this.value;
			
			this.value = value;
			
			valueChanged( oldValue );
		}
		
		/**
		 * Returns the current value of the parameter
		 */
		public function getValue(): *
		{
			return value;
		}
		
		/**
		 * Sets the current value of the parameter
		 * by passing a normalized value between 0 and 1
		 * 
		 * if changed, inform all callbacks
		 * 
		 * @param normalizedValue A normalized value between 0 and 1
		 */
		public function setValueNormalized( normalizedValue: Number ): void
		{
			var oldValue: * = value;
			
			value = mapping.map( normalizedValue );
			
			valueChanged( oldValue );
		}

		/**
		 * Returns the current normalized value of the parameter
		 * between 0 and 1
		 */
		public function getValueNormalized(): Number
		{
			return mapping.mapInverse( value );
		}
		
		/**
		 * Reset value to its initial default value
		 */
		public function reset(): void
		{
			setValue( defaultValue );
		}
		
		/**
		 * adds a callback function, invoked on value changed
		 * 
		 * @param callback The function, that will be invoked on value changed
		 */
		public function addChangedCallbacks( callback: Function ): void
		{
			changedCallbacks.push( callback );
		}

		/**
		 * removes a callback function
		 * 
		 * @param callback The function, that will be removed
		 */
		public function removeChangedCallbacks( callback: Function ): void
		{
			var index: int = changedCallbacks.indexOf( callback );
			
			if( index > -1 )
				changedCallbacks.splice( index, 1 );
		}
		
		private function valueChanged( oldValue: * ): void
		{
			if( oldValue == value )
				return;
			
			try
			{
				for each( var callback: Function in changedCallbacks )
					callback( this, oldValue, value );
			}
			catch( e: ArgumentError )
			{
				throw new ArgumentError( 'Make sure callbacks have the following signature: (parameter: Parameter, oldValue: *, newValue: *)' );
			}
		}
	}
	class MappingNumberLinear
		implements IMapping
	{
		private var min: Number;
		private var max: Number;
		
		public function MappingNumberLinear( min: Number = 0, max: Number = 1 )
		{
			this.min = min;
			this.max = max;
		}
		
		public function map( normalizedValue: Number ): *
		{
			return min + normalizedValue * ( max - min );
		}
		
		public function mapInverse( value: * ): Number
		{
			return ( value - min ) / ( max - min );
		}
	}
	class MappingIntLinear
		implements IMapping
	{
		private var min: Number;
		private var max: Number;
		
		public function MappingIntLinear( min: int = 0, max: int = 1 )
		{
			this.min = min;
			this.max = max;
		}
		
		public function map( normalizedValue: Number ): *
		{
			return rint( min + normalizedValue * ( max - min ) );
		}
		
		public function mapInverse( value: * ): Number
		{
			return ( value - min ) / ( max - min );
		}
	}
	class MappingBoolean
		implements IMapping
	{
		public function map( normalizedValue: Number ): *
		{
			return normalizedValue > .5;
		}
		
		public function mapInverse( value: * ): Number
		{
			return value ? 1 : 0;
		}
	}
	
	import flash.utils.ByteArray;
	
	class XMSample
	{
		public var length: uint;
		
		public var loopStart: uint;
		public var loopLength: uint;
		
		public var loop: Boolean;
		public var pingPong: Boolean;
		
		public var volume: uint;
		public var fineTone: int;
		
		public var type: uint;
		
		public var panning: uint;
		
		public var relativeNote: int;
		
		public var wave: Array;
		public var name: String;
		
		//-- quick&dirty bitboy compatibility
		public var repeatStart: uint;
		public var repeatEnd: uint;
		
		public function XMSample( stream: ByteArray, sampleHeaderSize: uint = 0x28 )
		{
			parse( stream, sampleHeaderSize );
		}
		
		private function parse( stream: ByteArray, sampleHeaderSize: uint ): void
		{
			var i: int;
			var p: int = stream.position;
			
			length = stream.readUnsignedInt();
			
			loopStart = repeatStart = stream.readUnsignedInt();
			loopLength = stream.readUnsignedInt(); repeatEnd = loopStart + loopLength;
			
			//NOTE: if sampleLoopLength == 0 then sample is NOT looping (even if sampleType or smth has it set)
			
			volume = stream.readUnsignedByte();
			fineTone = stream.readByte();
			
			type = stream.readUnsignedByte();
			panning = stream.readUnsignedByte();
			
			if ( ( type & 0x10 ) != 0 )
			{
				trace( 'Error! Found a 16b sample' );
				throw new XMFormatError( XMFormatError.NOT_IMPLEMENTED );
			}
			
			if ( ( type & 0x20 ) != 0 )
			{
				trace( 'Error! Found a stereo sample' );
				throw new XMFormatError( XMFormatError.NOT_IMPLEMENTED );
				
				if ( ( type & 0x10 ) != 0 )
				{
					// stereo 16b
				}
				else
				{
					// stereo 8b
				}
			}
			
			if ( ( type & 2 ) != 0 )
			{
				pingPong = true;
			}
			
			if ( ( type & 3 ) != 0 )
			{
				loop = true;
			}

			if ( loopLength == 0 )
				loop = false;
				
			relativeNote = stream.readByte();
			
			//-- unused
			stream.readByte();
			
			name = stream.readMultiByte( 22, XMFormat.ENCODING );


			stream.position = p + sampleHeaderSize;
					
			//-- decode delta-encoded sample
			var delta: int = 0;	
			
			wave = new Array( length );
			
			for ( i = 0; i < length; ++i )
			{
				delta += stream.readByte();
				wave[ i ] = delta;
			}
		}
		
		public function toString(): String
		{
			const ENUM: Array = ['length',
				'loopStart',
				'loopLength',
				'loop',
				'pingPong',
				'volume',
				'fineTone',
				'type',
				'panning',
				'relativeNote',
				'wave',
				'name'];
			
			var result: String = '[XMSample';
			
			for ( var i: int = 0; i < ENUM.length; i++ )
			{
				result += ( i == 0 ? ' ' : ', ' ) + ENUM[ i ] + ': ' + this[ENUM[i]];
			}
			
			return result + ']';
		}
	}
	
	import flash.utils.ByteArray;
	
	class XMInstrument
	{
		private static var INSTRUMENT_INDEX_ID: int = 0;
		
		public var header: XMInstrumentHeader;
		public var sampleHeader: XMSampleHeader;
		public var sample: XMSample;
		
		//debugging
		public var index: int;
		
		public function XMInstrument( stream: ByteArray, index: int )
		{
			this.index = index;
			parse( stream );
		}
		
		private function parse( stream: ByteArray ): void
		{
			var p: uint = stream.position;
			
			header = new XMInstrumentHeader( stream );
			
			if ( header.numSamples == 0 )
			{
				stream.position = p + header.size;
				return;
			}
			else
			{
				if ( header.numSamples > 1 )
				{
					throw new XMFormatError( XMFormatError.NOT_IMPLEMENTED );
				}
			}

			sampleHeader = new XMSampleHeader( stream );
				
			stream.position = p + header.size;
				
			sample = new XMSample( stream, sampleHeader.size );
		}
		
		public function toString(): String
		{
			return '[XMInstrument header: ' + header.toString() + ', sampleHeader: ' + ( sampleHeader == null ? 'null' : sampleHeader.toString() ) + ', sample: ' + ( sample == null ? 'null' : sample.toString() ) + ']';
		}
	}
	
	import flash.utils.ByteArray;
	
	class XMTrigger
	{
		private var instrumentIndex: uint;
		
		public var note: int;
		public var instrument: XMInstrument;
		
		public var volume: uint;
		public var volumeCommand: uint;
		public var hasVolume: Boolean;//is a volume command executed?
		
		public var effect: int;
		public var effectParam: int;
		public var hasEffect: Boolean;//is a effect command executed?
		
		public function XMTrigger( stream: ByteArray, instruments: Array )
		{
			parse( stream, instruments );
		}
		
		public function get period(): uint
		{//dirty hack for quick testing
			return note;
		}
		
		private function parse( stream: ByteArray, instruments: Array ): void
		{
			var type: int = stream.readUnsignedByte();
			
			volume = 0;
			volumeCommand = XMVolumeCommand.NO_COMMAND;
			
			if ( ( type & 0x80 ) != 0 )
			{
				if ( ( type & 0x01 ) != 0 ) note = stream.readUnsignedByte();
				if ( ( type & 0x02 ) != 0 ) instrumentIndex = stream.readUnsignedByte();
				if ( ( type & 0x04 ) != 0 )	volume = stream.readUnsignedByte();
				if ( ( type & 0x08 ) != 0 )	effect = stream.readUnsignedByte();
				if ( ( type & 0x10 ) != 0 )	effectParam = stream.readUnsignedByte();
			}
			else
			{
				note = type;
				instrumentIndex = stream.readUnsignedByte();
				volume = stream.readUnsignedByte();
				effect = stream.readUnsignedByte();
				effectParam = stream.readUnsignedByte();
			}
			
			if ( note == 97 )
			{
				// ModPlug displays these notes as == and sets
				// their value internal to 0xff
				note = 0xff;
			}
			else
			{
				if ( note > 0 && note < 97 )
				{
					note += 12;
					//do we need this?
				}
			}
			
			hasEffect = ( effect | effectParam ) != 0;
			
			if ( instrumentIndex == 0xff )
				instrumentIndex = 0;
				
			if ( instrumentIndex != 0 )
			{
				instrument = instruments[ int( instrumentIndex - 1 ) ];
			}
							
			if ( volume >= 0x10 && volume <= 0x50 )
			{
				volumeCommand = XMVolumeCommand.VOLUME;
				volume -= 0x10;
			}
			else if ( volume >= 0x60 )
			{
				volumeCommand = volume & 0xf0;
				volume &= 0x0f;
			}
			
			if ( volume == 0 && volumeCommand == XMVolumeCommand.NO_COMMAND )
			{
				hasVolume = false;
			}
			else
			{
				hasVolume = true;
			}
		}
		
		public function toString(): String
		{
			return '[XMTrigger instrument: ' + instrument + ', volume: ' + volume + ', effect: 0x' + effect.toString(0x10) + ', effectParam: 0x' + effectParam.toString(0x10) + ']';
		}
	}
	
	import flash.utils.ByteArray;
	
	class XMPattern
	{
		private var numRows: uint;		

		private var headerLength: uint;
		private var packingType: uint;

		private var packedDataSize: uint;
		private var dataOffset: uint;
		
		public var rows: Array;
		
		public function XMPattern( stream: ByteArray )
		{
			parse( stream );
		}
		
		public function row( index: int ): Array
		{
			return rows[ index ];
		}

		private function parse( stream: ByteArray ): void
		{
			headerLength = stream.readUnsignedInt();
			packingType = stream.readUnsignedByte();
			
			numRows = stream.readUnsignedShort();
			rows = new Array( numRows );
						
			packedDataSize = stream.readUnsignedShort();
			dataOffset = stream.position;
			
			//-- skip data for now
			stream.position += packedDataSize;
		}
		
		internal function parseData( stream: ByteArray, numChannels: uint, instruments: Array ): void
		{
			var i: int, j: int;
			
			stream.position = dataOffset;
			
			if ( packedDataSize <= 0 )
			{
				var emptyElement: ByteArray = new ByteArray;
				
				emptyElement.writeByte( 0x81 );//use packed note to read 0x80
				emptyElement.writeByte( 0x80 );

				for ( i = 0; i < numRows; i++ )
				{
					rows[ i ] = new Array( numChannels );
					
					for ( j = 0; j < numChannels; j++ )
					{
						emptyElement.position = 0;
						rows[ i ][ j ] = new XMTrigger( emptyElement, instruments );
					}
				}
			}
			else
			{
				for ( i = 0; i < numRows; i++ )
				{
					rows[ i ] = new Array( numChannels );
					
					for ( j = 0; j < numChannels; j++ )
					{
						rows[ i ][ j ] = new XMTrigger( stream, instruments );
					}
				}
			}
		}
		
		public function toString(): String
		{
			return '[XMPattern headerLength: ' + headerLength + ', packingType: ' + packingType + ', numRows: ' + numRows + ', packedDataSize: ' + packedDataSize + ']';
		}
		
		public function toASCII(): String
		{
			if ( packedDataSize == 0 )
				return '(empty)\n';
				
			var patternString: String = '';
			var numChannels: uint = rows[0].length;
			var row: Array;
			var line: String;
			
			for ( var i: int = 0; i < numRows; ++i )
			{
				row = rows[ i ];
				
				line = pad( i.toString() ) + ': ';
				
				for ( var j: int = 0; j < numChannels; ++j )
				{
					if ( j != 0 ) line += ' | ';
					
					var trigger: XMTrigger = row[ j ];
					
					if ( trigger.note == 0xff )
						line += '==';
					else
						line += ( trigger.note == 0 ? '..' : pad( trigger.note.toString() ) );
					
					line += ' ' + pad( ( trigger.instrument == null ? '..' : trigger.instrument.index.toString() ) );
					
					
					if ( !trigger.hasVolume )
					{
						line += ' ...';
					}
					else
					{
						line += ' ';
						
						switch ( trigger.volumeCommand )
						{
							case XMVolumeCommand.PANNING:				line += 'p'; break;
							case XMVolumeCommand.PANNING_SLIDE_LEFT:	line += 'l'; break;
							case XMVolumeCommand.PANNING_SLIDE_RIGHT:	line += 'r'; break;
							case XMVolumeCommand.TONE_PORTAMENTO:		line += 'g'; break;
							case XMVolumeCommand.VIBRATO:				line += 'v'; break;
							case XMVolumeCommand.VIBRATO_SPEED:			line += 'h'; break;
							case XMVolumeCommand.VOLUME:				line += 'v'; break;
							case XMVolumeCommand.VOLUME_FINE_DOWN:		line += 'b'; break;
							case XMVolumeCommand.VOLUME_FINE_UP:		line += 'a'; break;
							case XMVolumeCommand.VOLUME_SLIDE_DOWN:		line += 'd'; break;
							case XMVolumeCommand.VOLUME_SLIDE_UP:		line += 'c'; break;
							
							case XMVolumeCommand.NO_COMMAND:
							default:									line += '.'; break;
						}
						
						line += pad( trigger.volume.toString() );
					}
					
					if ( trigger.hasEffect )
					{
						line += ' ' + ( trigger.effect.toString(16).toUpperCase() );
						line += pad( ( trigger.effectParam.toString(16).toUpperCase() ) );
					}
					else
						line += ' ...';
				}
				
				patternString += line + '\n';
			}
			
			return patternString;
		}
		
		private function pad( input: String, toLength: uint = 2, paddingChar: String = '0' ): String
		{
			while ( input.length < toLength )
				input = paddingChar + input;
			
			return input;
		}
	}
	
	class XMFormatError extends Error
	{
		public static const FILE_CORRUPT: String = 'Invalid XM file';
		public static const NOT_IMPLEMENTED: String = 'A feature has not been implemented yet';
		
		public static const MAX_CHANNELS: String = 'Maximum number of channels reached';
		public static const MAX_INSTRUMENTS: String = 'Maximum number of instruments reached';
		public static const MAX_PATTERNS: String = 'Maximum number of patterns reached';
		public static const MAX_LENGTH: String = 'Maximum song length is reached';
		
		public function XMFormatError( message: String = '', id: int = 0 )
		{
			super( message, id );
		}
	}

	import flash.utils.ByteArray;
	
	/**
	 * The FormatBase class is an abstract descriptor for formats that are read by a tracker.
	 * Usually those formats are MOD, XM, IT or S3M.
	 * 
	 * @author Joa Ebert
	 */	
	class FormatBase
	{
		/**
		 * An Array of patterns.
		 * 
		 * A pattern is defined as an Array of rows. Each row is an Array of
		 * <code>numChannels</code> channels while each channel holds one
		 * trigger object.
		 */		
		protected var patterns: Array;
		
		/**
		 * An Array of pattern identifiers.
		 * 
		 * Each identifier is a uint. The <code>sequence</code> variable
		 * holds the order in which the patterns are played.
		 */
		protected var sequence: Array;
		
		/**
		 * The length of the <code>sequence</code> property.
		 * 
		 * Since <code>length</code> property stores the length of the <code>sequence</code>
		 * property it is important to keep in mind that the length given in number of patterns instead
		 * of seconds or milliseconds for instance.
		 */
		public var length: uint;
		
		/**
		 * The title of the current module.
		 */
		public var title: String;
		
		/**
		 * The number of channels.
		 */
		public var numChannels: uint;
		
		/**
		 * The length of the <code>patterns</code> property.
		 */
		public var numPatterns: uint;
		
		/**
		 * The credits of a song.
		 * 
		 * In MOD files the samples are used for the credits while in most
		 * of the XM files the instruments hold this information.
		 */
		public var credits: Array;
		
		/**
		* Default restart position in sequence once song is completed.
		*/		
		public var restartPosition: uint;
		
		/**
		 * Default bpm.
		 */		
		public var defaultBpm: uint;
		
		/**
		* Default speed.
		*/		
		public var defaultSpeed: uint;
		
		/**
		 * Creates a new FormatBase object.
		 * 
		 * Each property of the FormatBase will be set to its default value.
		 */	
		public function FormatBase( stream: ByteArray )
		{
			patterns = new Array;
			sequence = new Array;

			length = 0;		
			title = '';		

			numChannels = 0;
			numPatterns = 0;
			
			credits = new Array;
		}
		
		protected function parse( stream: ByteArray ): void 
		{
			
		}
		
		/**
		 * Finds the trigger object at given indices.
		 * 
		 * @param patternIndex The index of the pattern.
		 * @param rowIndex The index of the row.
		 * @param channelIndex The index of the channel.
		 * 
		 * @return The trigger at the given position.
		 */		
		public function getTriggerAt( patternIndex: uint, rowIndex: uint, channelIndex: uint ): TriggerBase
		{
			return TriggerBase( patterns[ patternIndex ][ rowIndex ][ channelIndex ] );
		}
		
		/**
		 * Finds the pattern index at given position in the sequence.
		 * 
		 * @param sequenceIndex The index in the sequence table.
		 * @return The pattern index at given position in the sequence.
		 */		
		public function getSequenceAt( sequenceIndex: uint ): uint
		{
			return uint( sequence[ sequenceIndex ] );
		}
		
		/**
		 * Returns the number of rows in the pattern at given index.
		 * 
		 * @param patternIndex The index of the pattern.
		 * @return Number of rows.
		 */		
		public function getPatternLength( patternIndex: uint ): uint
		{
			return ( patterns[ patternIndex ] as Array ).length;
		}
		
		/**
		 * Creates and reurns an array of channels.
		 * 
		 * @return An array of channels.
		 */		
		public function getChannels( bitboy: BitBoy ): Array
		{
			return null;
		}
		
		/**
		 * Creates and returns the string representation of the object.
		 * @return The string represenation of the object.
		 */		
		public function toString(): String
		{
			return '[FormatBase]';
		}
	}


	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	class XMFormat extends FormatBase
	{
		internal static const ENCODING: String = 'us-ascii';
		
		private static const MAX_CHANNELS: uint = 0x20;
		private static const MAX_INSTRUMENTS: uint = 0x80;
		private static const MAX_PATTERNS: uint = 0x100;
		private static const MAX_LENGTH: uint = 0x100;
		
		//public var patterns: Array;
		
		//public var sequence: Array; //pattern order
		//public var length: uint; //sequence length
		//public var restartPosition: uint;
		
		//public var title: String;
		
		//public var numChannels: uint;
		//public var numPatterns: uint;
		public var numInstruments: uint;
		
		public var useLinearSlides: Boolean;
		
		public var defaultTempo: uint;
		public var defaultBPM: uint;
		
		private var instruments: Array;
		
		static public function decode( stream: ByteArray ): XMFormat
		{
			return new XMFormat( stream );
		}
		
		public function XMFormat( stream: ByteArray )
		{
			super( stream );
			
			instruments = new Array;
			
			parse( stream );
		}
		
		override protected function parse( stream: ByteArray ): void
		{
			var i: int;
			
			stream.position = 0;
			stream.endian = Endian.LITTLE_ENDIAN;
			
			var idText: String = stream.readMultiByte( 17, ENCODING );
			title = stream.readMultiByte( 20, ENCODING );

			if ( idText.toLowerCase() != 'extended module: ' )
				throw new XMFormatError( XMFormatError.FILE_CORRUPT );
				
			if ( stream.readUnsignedByte() != 0x1a )
				throw new XMFormatError( XMFormatError.FILE_CORRUPT );
				
			var trackerName: String = stream.readMultiByte( 20, ENCODING );
			
			var version: uint = stream.readUnsignedShort();
	
			if ( version > 0x0104 )//01 = major, 04 = minor
				throw new XMFormatError( XMFormatError.NOT_IMPLEMENTED );
			
			var headerSize: uint = stream.readUnsignedInt();
			
			length = stream.readUnsignedShort();//songLength in patterns
			
			if ( length > MAX_LENGTH )
				throw new XMFormatError( XMFormatError.MAX_LENGTH );
			
			restartPosition = stream.readUnsignedShort();
			
			numChannels = stream.readUnsignedShort();
			
			if ( numChannels > MAX_CHANNELS )
				throw new XMFormatError( XMFormatError.MAX_CHANNELS );
			
			numPatterns = stream.readUnsignedShort();
			
			if ( numPatterns > MAX_PATTERNS )
				throw new XMFormatError( XMFormatError.MAX_PATTERNS );
			
			numInstruments = stream.readUnsignedShort();
			
			if ( numInstruments > MAX_INSTRUMENTS )
				throw new XMFormatError( XMFormatError.MAX_INSTRUMENTS );
			
			var flags: uint = stream.readUnsignedShort();
			
			useLinearSlides = ( ( flags & 1 ) == 1 );
			
			defaultTempo = stream.readUnsignedShort();
			
			defaultBPM = stream.readUnsignedShort();
			
			sequence = new Array( length );
			
			for ( i = 0; i < length; ++i )
			{
				sequence[ i ] = stream.readUnsignedByte();
			}
			
			stream.position += 0x100 - length;
			
			//-- seek to instruments by getting pattern headers
			for ( i = 0; i < numPatterns; ++i )
			{
				patterns.push( new XMPattern( stream ) );
			}
			
			//-- parse instruments
			for ( i = 0; i < numInstruments; ++i )
			{
				instruments.push( new XMInstrument( stream, i + 1 ) );
			}
			
			//-- parse pattern data now
			for ( i = 0; i < numPatterns; ++i )
			{
				XMPattern( patterns[ i ] ).parseData( stream, numChannels, instruments );
			}
			
			// access a trigger:
			// patternId = id of pattern
			// rowNumber = number of row in pattern
			// channelNumber = desired channel
			//
			// XMTrigger( XMPattern( patterns[ patternId ] ).rows[ rowNumber ][ channelNumber ] )
		}
	}
	
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	/**
	 * @private
	 */
	
	class ModSample
	{
		public var title: String;
		public var length: int;
		public var tone: int;
		public var volume: int;
		public var repeatStart: int;
		public var repeatLength: int;
		public var waveform: ByteArray;
		public var wave: Array;
		
		public function ModSample( stream: ByteArray )
		{
			if( stream )
				parse( stream );
		}
		
		public function loadWaveform( stream: ByteArray ): void
		{
			if ( length == 0 )
				return;

			waveform = new ByteArray();
			
			wave = new Array();
			
			var value: Number;
			var min: Number = 1;
			var max: Number = -1;
			
			var i: int;
			
			for( i = 0 ; i < length ; i++ )
			{
				value = ( stream.readByte() + .5 ) / 127.5;
				
				if( value < min ) min = value;
				if( value > max ) max = value;
				
				wave.push( value );
			}
			
			var base: Number = ( min + max ) / 2;
			
			for( i = 0 ; i < length ; i++ )
				wave[i] -= base;
		}
		
		private function parse( stream: ByteArray ): void
		{
			stream.position = 0;			
			title = '';
			
			//-- read 22 chars into the title
			//   we dont break if we reach the NUL char cause this would turn
			//   the stream.position wrong
			for ( var i: int = 0; i < 22; i++ )
			{
				var char: uint = uint( stream.readByte() );
				if ( char != 0 )
					title += String.fromCharCode( char );
			}
			
			length = stream.readUnsignedShort();
			tone = stream.readUnsignedByte(); //everytime 0
			volume = stream.readUnsignedByte();
			repeatStart = stream.readUnsignedShort();
			repeatLength = stream.readUnsignedShort();

			//-- turn it into bytes
			length <<= 1;
			repeatStart <<= 1;
			repeatLength <<= 1;
		}
		
		public function clone(): ModSample
		{
			var sample: ModSample = new ModSample( null );
			
			sample.title = title;
			sample.length = length;
			sample.tone = tone;
			sample.volume = volume;
			sample.repeatStart = repeatStart;
			sample.repeatLength = repeatLength;
			sample.waveform = waveform;
			sample.wave = wave;
			
			return sample;
		}
		
		public function toString(): String
		{
			return '[MOD Sample'
				+ ' title: '+ title
				+ ', length: ' + length
				+ ', tone: ' + tone
				+ ', volume: ' + volume
				+ ', repeatStart: ' + repeatStart
				+ ', repeatLength: ' + repeatLength
				+ ']';
		}
	}
	

	import flash.display.Loader;
	import flash.events.Event;
	import flash.media.Sound;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	/**
	 * The class SoundFactory provides creating a valid
	 * flash.media.Sound object by passing either a
	 * custom Array with de.popforge.audio.output.Sample
	 * entries or by passing an uncompressed PCM ByteArray.
	 * 
	 * @author Andre Michelle
	 */
	class SoundFactory
	{
		/**
		 * Creates a flash.media.Sound object from dynamic audio material
		 * 
		 * @param samples An Array of Samples (de.popforge.audio.output.Sample)
		 * @param channels Mono(1) or Stereo(2)
		 * @param bits 8bit(8) or 16bit(16)
		 * @param rate SamplingRate 5512Hz, 11025Hz, 22050Hz, 44100Hz
		 * @param onComplete Function, that will be called after the Sound object is created. The signature must accept the Sound object as a parameter!
		 * 
		 * @see http://livedocs.adobe.com/flex/2/langref/flash/media/Sound.html flash.media.Sound
		 */
		static public function fromArray( samples: Array, channels: uint, bits: uint, rate: uint, onComplete: Function ): void
		{
			var bytes: ByteArray = new ByteArray();
			bytes.endian = Endian.LITTLE_ENDIAN;
			
			var i: int;
			var s: Sample;
			var l: Number;
			var r: Number;
			
			var _numSamples: int = samples.length;
			
			switch( channels )
			{
				case Audio.MONO:

					if( bits == Audio.BIT16 )
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = s.left;
							
							if( l < -1 ) bytes.writeShort( -0x7fff );
							else if( l > 1 ) bytes.writeShort( 0x7fff );
							else bytes.writeShort( l * 0x7fff );
							
							s.left = s.right = 0;
						}
					}
					else
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = s.left;
							
							if( l < -1 ) bytes.writeByte( 0 );
							else if( l > 1 ) bytes.writeByte( 0xff );
							else bytes.writeByte( 0x80 + l * 0x7f );
							
							s.left = s.right = 0;
						}
					}
					break;
					
				case Audio.STEREO:

					if( bits == Audio.BIT16 )
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = s.left;
							r = s.right;
							
							if( l < -1 ) bytes.writeShort( -0x7fff );
							else if( l > 1 ) bytes.writeShort( 0x7fff );
							else bytes.writeShort( l * 0x7fff );
			
							if( r < -1 ) bytes.writeShort( -0x7fff );
							else if( r > 1 ) bytes.writeShort( 0x7fff );
							else bytes.writeShort( r * 0x7fff );
							
							s.left = s.right = 0;
						}
					}
					else
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = s.left;
							r = s.right;
							
							if( l < -1 ) bytes.writeByte( 0 );
							else if( l > 1 ) bytes.writeByte( 0xff );
							else bytes.writeByte( 0x80 + l * 0x7f );
							if( r < -1 ) bytes.writeByte( 0 );
							else if( r > 1 ) bytes.writeByte( 0xff );
							else bytes.writeByte( 0x80 + r * 0x7f );
							
							s.left = s.right = 0;
						}
					}
					break;
			}
			
			SoundFactory.fromByteArray( bytes, channels, bits, rate, onComplete );
		}

		/**
		 * Creates a flash.media.Sound object from dynamic audio material
		 * 
		 * @param samples A uncompressed PCM ByteArray
		 * @param channels Mono(1) or Stereo(2)
		 * @param bits 8bit(8) or 16bit(16)
		 * @param rate SamplingRate 5512Hz, 11025Hz, 22050Hz, 44100Hz
		 * @param onComplete Function, that will be called after the Sound object is created. The signature must accept the Sound object as a parameter!
		 * 
		 * @see http://livedocs.adobe.com/flex/2/langref/flash/media/Sound.html flash.media.Sound
		 */
		static public function fromByteArray( bytes: ByteArray, channels: uint, bits: uint, rate: uint, onComplete: Function ): void
		{
			Audio.checkAll( channels, bits, rate );
			
			//-- get naked swf bytearray
			var swf: ByteArray = ByteArray(  new Base64(SWF.bin) );

			swf.endian = Endian.LITTLE_ENDIAN;
			swf.position = swf.length;

			//-- write define sound tag header
			swf.writeShort( 0x3bf );
			swf.writeUnsignedInt( bytes.length + 7 );

			//-- assemble audio property byte (uncompressed little endian)
			var byte2: uint = 3 << 4;
	
			switch( rate )
			{
				case 44100: byte2 |= 0xc; break;
				case 22050: byte2 |= 0x8; break;
				case 11025:	byte2 |= 0x4; break;
			}

			var numSamples: int = bytes.length;
			
			if( channels == 2 )
			{
				byte2 |= 1;
				numSamples >>= 1;
			}
			
			if( bits == 16 )
			{
				byte2 |= 2;
				numSamples >>= 1;
			}
	
			//-- write define sound tag
			swf.writeShort( 1 );
			swf.writeByte( byte2 );
			swf.writeUnsignedInt( numSamples );
			swf.writeBytes( bytes );

			//-- write eof tag in swf stream
			swf.writeShort( 1 << 6 );
			
			//-- overwrite swf length
			swf.position = 4;
			swf.writeUnsignedInt( swf.length );
			swf.position = 0;
			
			var onSWFLoaded: Function = function( event: Event ): void
			{
				onComplete( Sound( new ( loader.contentLoaderInfo.applicationDomain.getDefinition( 'SoundItem' ) as Class )() ) );
			};
			
			var loader: Loader = new Loader();
			loader.contentLoaderInfo.addEventListener( Event.COMPLETE, onSWFLoaded );
			loader.loadBytes( swf );
		}
	}
	
	class Audio
	{
		static public const MONO: uint = 1;
		static public const STEREO: uint = 2;
		
		static public const BIT8: uint = 8;
		static public const BIT16: uint = 16;
		
		static public const RATE44100: uint = 44100;
		static public const RATE22050: uint = 22050;
		static public const RATE11025: uint = 11025;
		static public const RATE5512: uint = 5512;

		/**
		 * Checks all reasonable audio properties
		 * @throws Error thrown, if any property has not valid value
		 * 
		 * @param channels Mono(1) or Stereo(2)
		 * @param bits 8bit(8) or 16bit(16)
		 * @param rate SamplingRate 5512Hz, 11025Hz, 22050Hz, 44100Hz
		 */
		static public function checkAll( channels: uint, bits: uint, rate: uint ): void
		{
			checkChannels( channels );
			checkBits( bits );
			checkRate( rate );
		}
		
		/**
		 * Checks if the passed number of channels if valid
		 * @throws Error thrown, if not Mono(1) or Stereo(2)
		 * 
		 * @param channels Mono(1) or Stereo(2)
		 */
		static public function checkChannels( channels: uint ): void
		{
			switch( channels )
			{
				case MONO:
				case STEREO:
					break;

				default:
					throw new Error( 'Only mono or stereo is supported.' );
			}
		}

		/**
		 * Checks if the passed number of bits if valid
		 * @throws Error thrown, if not 8bit(8) or 16bit(16)
		 * 
		 * @param bits 8bit(8) or 16bit(16)
		 */
		static public function checkBits( bits: uint ): void
		{
			switch( bits )
			{
				case BIT8:
				case BIT16:
					break;
				
				default:
					throw new Error( 'Only 8 and 16 bit is supported.' );
			}
		}

		/**
		 * Checks if the passed number of bits if valid
		 * @throws Error thrown, if not 5512Hz, 11025Hz, 22050Hz, 44100Hz
		 * 
		 * @param rate SamplingRate 5512Hz, 11025Hz, 22050Hz, 44100Hz
		 */
		static public function checkRate( rate: uint ): void
		{
			switch( rate )
			{
				case RATE44100:
				case RATE22050:
				case RATE11025:
				case RATE5512:
					break;

				default:
					throw new Error( rate.toString() + 'is not supported.' );
			}
		}
	}
	
	
	import flash.events.Event;
	import flash.media.Sound;
	import flash.media.SoundChannel;
	import flash.utils.ByteArray;
	import flash.utils.Endian;
	/**
	 * The class AudioBuffer creates an endless AudioStream
	 * as long as the buffer is updated with new samples
	 * 
	 * @author Andre Michelle
	 */
	class AudioBuffer
	{
		/**
		 * The internal minimal sound buffer length in samples(PC)
		 */
		static public const UNIT_SAMPLES_NUM: uint = 2048;
		
		/**
		 * Stores a delegate function called, when the AudioBuffer is inited.
		 */
		public var onInit: Function;
		/**
		 * Stores a delegate function called, when the AudioBuffer has complete its cycle.
		 */
		public var onComplete: Function;
		/**
		 * Stores a delegate function called, when the AudioBuffer is started.
		 */
		public var onStart: Function;
		/**
		 * Stores a delegate function called, when the AudioBuffer is stopped.
		 */
		public var onStop: Function;
		
		private var multiple: uint;
		private var channels: uint;
		private var bits: uint;
		private var rate: uint;
		
		private var sync: Sound;
		private var syncChannel: SoundChannel;
		private var sound: Sound;
		private var soundChannel: SoundChannel;
		
		private var _numBytes: uint;
		private var _numSamples: uint;

		private var bytes: ByteArray;
		private var samples: Array;

		private var firstrun: Boolean;
		private var playing: Boolean;
		
		private var $isInit: Boolean;
		
		/**
		 * Creates an AudioBuffer instance.
		 * 
		 * @param multiple Defines the buffer length (4times recommended)
		 * @param channels Mono(1) or Stereo(2)
		 * @param bits 8bit(8) or 16bit(16)
		 * @param rate SamplingRate 5512Hz, 11025Hz, 22050Hz, 44100Hz
		 */
		public function AudioBuffer( multiple: uint, channels: uint, bits: uint, rate: uint )
		{
			Audio.checkAll( channels, bits, rate );
			
			this.multiple = multiple;
			this.channels = channels;
			this.bits = bits;
			this.rate = rate;
			
			firstrun = true;
			
			init();
		}

		/**
		 * Updates the AudioBuffer samples for the next playback cycle
		 * Must be called after the new samples are computed
		 */
		public function update(): ByteArray
		{
			bytes.length = 0;
			
			var i: int;
			var s: Sample;
			var l: Number;
			var r: Number;
			
			switch( channels )
			{
				case Audio.MONO:

					if( bits == Audio.BIT16 )
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = ( s.left + s.right ) / 2;
							
							if( l < -1 ) bytes.writeShort( -0x7fff );
							else if( l > 1 ) bytes.writeShort( 0x7fff );
							else bytes.writeShort( l * 0x7fff );
							
							s.left = s.right = 0;
						}
					}
					else
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = ( s.left + s.right ) / 2;
							
							if( l < -1 ) bytes.writeByte( 0 );
							else if( l > 1 ) bytes.writeByte( 0xff );
							else bytes.writeByte( 0x80 + l * 0x7f );
							
							s.left = s.right = 0;
						}
					}
					break;
					
				case Audio.STEREO:

					if( bits == Audio.BIT16 )
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = s.left;
							r = s.right;
							
							if( l < -1 ) bytes.writeShort( -0x7fff );
							else if( l > 1 ) bytes.writeShort( 0x7fff );
							else bytes.writeShort( l * 0x7fff );
			
							if( r < -1 ) bytes.writeShort( -0x7fff );
							else if( r > 1 ) bytes.writeShort( 0x7fff );
							else bytes.writeShort( r * 0x7fff );
							
							s.left = s.right = 0;
						}
					}
					else
					{
						for( i = 0 ; i < _numSamples ; i++ )
						{
							s = samples[i];
							l = s.left;
							r = s.right;
							
							if( l < -1 ) bytes.writeByte( 0 );
							else if( l > 1 ) bytes.writeByte( 0xff );
							else bytes.writeByte( 0x80 + l * 0x7f );
							if( r < -1 ) bytes.writeByte( 0 );
							else if( r > 1 ) bytes.writeByte( 0xff );
							else bytes.writeByte( 0x80 + r * 0x7f );
							
							s.left = s.right = 0;
						}
					}
					break;
			}
			
			SoundFactory.fromByteArray( bytes, channels, bits, rate, onNewBufferCreated );
			
			return bytes;
		}
		
		/**
		 * Starts the AudioBuffer playback
		 */
		public function start(): Boolean
		{
			if( playing ) return false;
			
			if( sync != null )
			{
				syncChannel = sync.play( 0, 1 );
				syncChannel.addEventListener( Event.SOUND_COMPLETE, onSyncComplete );
				
				if( soundChannel != null )
					soundChannel.stop();
				
				if( sound != null )
					soundChannel = sound.play( 0, 1 );
				
				playing = true;
			}
			
			if( onStart != null )
				onStart( this );
			
			return true;
		}

		/**
		 * Stops the AudioBuffer playback
		 */
		public function stop(): Boolean
		{
			if( !playing ) return false;
			
			if( syncChannel != null )
			{
				syncChannel.stop();
				syncChannel = null;
			}

			if( soundChannel != null )
			{
				soundChannel.stop();
				soundChannel = null;
			}
			
			playing = false;
			sound = null;
			
			var s: Sample;

			for( var i: int = 0 ; i < _numSamples ; i++ )
			{
				s = samples[i];
				s.left = s.right = 0.0;
			}
			
			if( onStop != null )
				onStop( this );
			
			return true;
		}
		
		/**
		 * Returns true, if the AudioBuffer is playing
		 */
		public function isPlaying(): Boolean
		{
			return playing;
		}

		/**
		 * Sets the number of channels. Stops AudioBuffer playback for new a init phase
		 */
		public function setChannels( channels: uint ): void
		{
			Audio.checkChannels( channels );
			
			if( channels != this.channels )
			{
				this.stop();
				this.channels = channels;
				this.init();
			}
		}

		/**
		 * Returns number of channels.
		 */
		public function getChannels(): uint
		{
			return channels;
		}

		/**
		 * Sets the number of bits. Stops AudioBuffer playback for new a init phase
		 */
		public function setBits( bits: uint ): void
		{
			Audio.checkBits( bits );
			
			if( bits != this.bits )
			{
				this.stop();
				this.bits = bits;
				this.init();
			}
		}

		/**
		 * Returns number of bits.
		 */
		public function getBits(): uint
		{
			return bits;
		}

		/**
		 * Sets the samplingRate. Stops AudioBuffer playback for new a init phase
		 */
		public function setRate( rate: uint ): void
		{
			Audio.checkRate( rate );
			
			if( rate != this.rate )
			{
				this.stop();
				this.rate = rate;
				this.init();
			}
		}

		/**
		 * Returns samplingRate in Hz
		 */
		public function getRate(): uint
		{
			return rate;
		}

		/**
		 * Sets the length of the buffer. Stops AudioBuffer playback for new a init phase
		 */
		public function setMultiple( multiple: uint ): void
		{
			if( multiple != this.multiple )
			{
				this.stop();
				this.multiple = multiple;
				this.init();
			}
		}

		/**
		 * Returns length of the buffer (sampleNum / UNIT_SAMPLES_NUM)
		 */
		public function getMultiple(): uint
		{
			return multiple;
		}

		/**
		 * Returns samples for overriding with new amplitudes
		 */
		public function getSamples(): Array
		{
			return samples;
		}

		/**
		 * Returns number of samples
		 */
		public function get numSamples(): uint	//-- read only
		{
			return _numSamples;
		}

		/**
		 * Returns current peak(left)
		 */
		public function get leftPeak(): Number
		{
			return soundChannel == null ? 0 : soundChannel.leftPeak;
		}

		/**
		 * Returns current peak(right)
		 */
		public function get rightPeak(): Number
		{
			return soundChannel == null ? 0 : soundChannel.rightPeak;
		}

		/**
		 * Returns true, if the AudioBuffer is inited
		 */
		public function get isInit(): Boolean
		{
			return $isInit;
		}

		/**
		 * Returns number of milliseconds of each buffer
		 */
		public function get millisEachBuffer(): Number
		{
			return 2048000 / rate * multiple;
		}
		
		private function init(): void
		{
			$isInit = false;
			
			if( multiple == 0 )
				throw new Error( 'Buffer must have a length greater than 0.' );
			
			var i: int;
			
			bytes = new ByteArray();
			bytes.endian = Endian.LITTLE_ENDIAN;
			
			//-- compute number of bytes
			switch( rate )
			{
				case Audio.RATE44100:
					_numSamples = ( UNIT_SAMPLES_NUM * multiple ); break;
				case Audio.RATE22050:
					_numSamples = ( UNIT_SAMPLES_NUM * multiple ) >> 1; break;
				case Audio.RATE11025:
					_numSamples = ( UNIT_SAMPLES_NUM * multiple ) >> 2; break;
				case Audio.RATE5512:
					_numSamples = ( UNIT_SAMPLES_NUM * multiple ) >> 3; break;
			}
			
			//-- compute number of bytes
			_numBytes = _numSamples;
			if( channels == Audio.STEREO ) _numBytes <<= 1;
			if( bits == Audio.BIT16 ) _numBytes <<= 1;

			samples = new Array();
			for( i = 0 ; i < _numSamples ; i++ )
				samples.push( new Sample() );

			//-- create silent bytes for sync sound
			var syncSamples: ByteArray = new ByteArray();
			
			switch( bits )
			{
				case Audio.BIT16:
					syncSamples.length = ( _numSamples - 1 ) << 1; break;
				case Audio.BIT8:
					syncSamples.length = _numSamples - 1;
					for( i = 0 ; i < syncSamples.length ; i++ )
						syncSamples[i] = 128;
					break;
			}

			SoundFactory.fromByteArray( syncSamples, 1, bits, rate, onGenerateSyncSound );
			
			playing = false;
		}
		
		private function onGenerateSyncSound( sound: Sound ): void
		{
			sync = sound;
			
			$isInit = true;
			
			if( onInit != null && firstrun )
				onInit( this );
			
			firstrun = false;
		}

		private function onNewBufferCreated( sound: Sound ): void
		{
			if( playing )
				this.sound = sound;
		}
		
		private function onSyncComplete( event: Event ): void
		{
			if( syncChannel != null )
				syncChannel.stop();

			syncChannel = sync.play( 0, 1 );
			syncChannel.addEventListener( Event.SOUND_COMPLETE, onSyncComplete );
			
			if( soundChannel != null )
				soundChannel.stop();

			if( sound != null )
			{
				soundChannel = sound.play( 0, 1 );
			}

			sound = null;
			
			onComplete( this );
		}
	}

	class Sample
	{
		/**
		 * The left amplitude of the Sample
		 */
		public var left: Number;
		/**
		 * The right amplitude of the Sample
		 */
		public var right: Number;

		/**
		 * Creates a Sample instance
		 * 
		 * @param left The left amplitude of the Sample
		 * @param right The right amplitude of the Sample
		 */
		public function Sample( left: Number = 0.0, right: Number = 0.0 )
		{
			this.left = left;
			this.right = right;
		}
		
		/**
		 * Returns a clone of the current Sample
		 */
		public function clone(): Sample
		{
			return new Sample( left, right );
		}
		
		public function toString(): String
		{
			return '{ left: ' + left + ' right: ' + right + ' }';
		}
	}

	class TriggerBase
	{
		/**
		* The effect that is initialized with this trigger.
		*/		
		public var effect: uint;
		
		/**
		* The parameter for the effect.
		*/	
		public var effectParam: uint;
		
		/**
		* If the trigger has any impact on effects or not.
		*/		
		public var hasEffect: Boolean;
		
		/**
		* The period for the trigger.
		*/		
		public var period: uint;


		/**
		 * Creates a new TriggerBase object.
		 * 
		 * Each property of the TriggerBase will be set to its default value.
		 */	
		public function TriggerBase()
		{
			effect = 0;
			effectParam = 0;
			
			hasEffect = false;
			
			period = 0;
		}
		
		/**
		 * Creates and returns the string representation of the object.
		 * @return The string represenation of the object.
		 */	
		public function toString(): String
		{
			return '[TriggerBase]';
		}
	}

	import flash.utils.ByteArray;
	
	/**
	 * @private
	 */	
	
	class ModTrigger extends TriggerBase
	{
		//public var effect: uint;
		//public var effectParam: uint;
		//public var period: uint;
		public var modSample: ModSample;
		
		public function ModTrigger( stream: ByteArray, modSamples: Array )
		{
			parse( stream, modSamples );
		}
		
		private function parse( stream: ByteArray, modSamples: Array ): void
		{
			/*
			 Byte 0    Byte 1   Byte 2   Byte 3
			 aaaaBBBB CCCCCCCCC DDDDeeee FFFFFFFFF
			
			 aaaaDDDD     = sample number
			 BBBBCCCCCCCC = sample period value
			 eeee         = effect number
			 FFFFFFFF     = effect parameters
			*/
			
			var b0: int = stream.readUnsignedByte();
			var b1: int = stream.readUnsignedByte();
			var b2: int = stream.readUnsignedByte();
			var b3: int = stream.readUnsignedByte();
			
			modSample = modSamples[ ( b0 & 0xf0 ) | ( b2 >> 4 ) ];
			period = ( ( b0 & 0x0f ) << 8 ) | b1;
			effect = b2 & 0x0F;
			effectParam = b3;
		}
		
		override public function toString(): String
		{
			return '[MOD Trigger'
				+ ' modSample: '+ modSample
				+ ', period: ' + period
				+ ', effect: ' + effect
				+ ', effectParam: ' + effectParam
				+ ']';
		}
	}

	class ChannelBase
	{
		protected var bitboy: BitBoy;
		protected var id: int;
		protected var pan: Number;
		
		protected var trigger: TriggerBase;
		
		/* PITCH */
		protected var tone: int;
		protected var period: Number;
		
		protected var linearPeriod: Number = 0;
		
		/* EFFECT */
		protected var effect: int;
		protected var effectParam: int;
		
		protected var mute: Boolean;
		
		public function ChannelBase( bitboy: BitBoy, id: int, pan: Number )
		{
			this.bitboy = bitboy;
			this.pan = pan;
			this.id = id;
		}
		
		public function setMute( value: Boolean ): void
		{
			mute = value;
		}
		
		public function reset(): void
		{
			throw new Error( 'Override Implementation!' );
		}
		
		public function onTrigger( trigger: TriggerBase ): void
		{
			throw new Error( 'Override Implementation!' );
		}
		
		public function onTick( tick: int ): void
		{
			throw new Error( 'Override Implementation!' );
		}
		
		public function processAudioAdd( samples: Array ): void
		{
			throw new Error( 'Override Implementation!' );
		}
			
		public function toString(): String
		{
			return '[ChannelBase]';
		}
	}
	
	import flash.utils.ByteArray;
	import flash.utils.Endian;

	/**
	 * @private
	 */

	class ModFormat extends FormatBase
	{
		static public function decode( stream: ByteArray ): ModFormat
		{
			return new ModFormat( stream );
		}
		
		// ModFormat specific
		public var modSamples: Array;

		//-- MOD format
		public var format: String;
			
		//-- define some positions in the file
		private const P_FORMAT: uint = 0x438;
		private const P_LENGTH: uint = 0x3b6;
		private const P_SEQUENCE: uint = 0x3b8;
		private const P_PATTERNS: uint = 0x43c;
		
		public function ModFormat( stream: ByteArray )
		{
			super( stream );
			
			modSamples = new Array( 32 );
			format = '';

			numChannels = 4;
					
			restartPosition = 0;

			defaultBpm = 125;
			defaultSpeed = 6;
					
			parse( stream );
		}
		
		override public function getChannels( bitboy: BitBoy ):Array
		{
			return [
					new ModChannel( bitboy, 0, -1 ),
					new ModChannel( bitboy, 1,  1 ),
					new ModChannel( bitboy, 2,  1 ),
					new ModChannel( bitboy, 3, -1 )
				];
		}
		
		override protected function parse( stream: ByteArray ): void
		{
			stream.endian = Endian.LITTLE_ENDIAN;
			stream.position = P_FORMAT;
			
			var patternNum: int = 0;
			
			//-- mod format
			format = String.fromCharCode( stream.readByte() ) + String.fromCharCode( stream.readByte() ) +
  					 String.fromCharCode( stream.readByte() ) + String.fromCharCode( stream.readByte() );
			
			if ( format.toLocaleLowerCase() != 'm.k.' )
				throw new Error( 'Unsupported MOD format' );
			
			 var i: int;
			
			//-- title
			title = '';
			stream.position = 0;
			for ( i = 0; i < 20; i++ )
			{
				var char: uint = stream.readUnsignedByte();			
				if ( char == 0 )
					break;	
				title += String.fromCharCode( char );
			}
			
			//-- sequence length
			stream.position = P_LENGTH;
			length = stream.readUnsignedByte();
			
			//-- samples
			var bytes: ByteArray = new ByteArray();
        	for ( i = 1; i <= 31; i++ )
        	{  		
            	stream.position = ( i - 1 ) * 0x1e + 0x14;
				bytes.position = 0;
				
				stream.readBytes( bytes, 0, 30 );	

				modSamples[ i ] = new ModSample( bytes );
	        }
	        
	        //-- sequence
	        stream.position = P_SEQUENCE;
			sequence = new Array( length );
				
			for ( i = 0; i < length; i++ )
			{
				sequence[ i ] = stream.readUnsignedByte();
				
				if ( sequence[ i ] > patternNum ) 
					patternNum = sequence[ i ];
			}
			
			
			numPatterns = patternNum;
		
			//-- patterns
			for ( i = 0; i < patternNum + 1; i++ )
			{
				//-- 4bytes * 4channels * 64rows = 0x400bytes
				stream.position = P_PATTERNS + i * 0x400;
				
				patterns[ i ] = new Array( 64 );
				
				for ( var j: int = 0; j < 64; j++ )
				{
					patterns[ i ][ j ] = new Array( 4 );
					for ( var k: int = 0; k < 4; k++ )
					{
						patterns[ i ][ j ][ k ] = new ModTrigger( stream, modSamples );
						
						//if( k == 0 )
						//{
						//	trace( j, patterns[ i ][ j ][ k ] );
						//}
					}
				}
			}
			
			//-- waveforms
			var sample: ModSample;
			for ( i = 1; i <= 31; i++ )
			{
				sample = ModSample( modSamples[ i ] );
				sample.loadWaveform( stream );	
			}
			
			//-- credits
			var modSample: ModSample;

			for( i = 1; i < modSamples.length; i++ )
			{
				modSample = modSamples[i];
				
				if( modSample.title != '' )
				{
					credits.push( modSample.title );
				}
			}
		}
	}

	
	class ModChannel extends ChannelBase
	{
		static private const ARPEGGIO: int = 0x0;
		static private const PORTAMENTO_UP: int = 0x1;
		static private const PORTAMENTO_DN: int = 0x2;
		static private const TONE_PORTAMENTO: int = 0x3;
		static private const VIBRATO: int = 0x4;
		static private const TONE_PORTAMENTO_VOLUME_SLIDE: int = 0x5;
		static private const VIBRATO_VOLUME_SLIDE: int = 0x6;
		static private const TREMOLO: int = 0x7;
		static private const SET_PANNING: int = 0x8;
		static private const SAMPLE_OFFSET: int = 0x9;
		static private const VOLUME_SLIDE: int = 0xa;
		static private const POSITION_JUMP: int = 0xb;
		static private const SET_VOLUME: int = 0xc;
		static private const PATTERN_BREAK: int = 0xd;
		static private const EXTENDED_EFFECTS: int = 0xe;
		static private const SET_SPEED: int = 0xf;
		
		static private const TONE_TABLE: Array =
		[
			856,808,762,720,678,640,604,570,538,508,480,453,
			428,404,381,360,339,320,302,285,269,254,240,226,
			214,202,190,180,170,160,151,143,135,127,120,113
		];
		
		static private const SINE_TABLE: Array =
		[
			0,24,49,74,97,120,141,161,
	 		180,197,212,224,235,244,250,253,
	 		255,253,250,244,235,224,212,197,
	 		180,161,141,120,97,74,49,24,
			0,-24,-49,-74,-97,-120,-141,-161,
	 		-180,-197,-212,-224,-235,-244,-250,-253,
	 		-255,-253,-250,-244,-235,-224,-212,-197,
	 		-180,-161,-141,-120,-97,-74,-49,-24
		];
		
		private var wave: Array;
		private var wavePhase: Number;
		private var repeatStart: int;
		private var repeatLength: int;
		private var firstRun: Boolean;
		private var volume: int;
		
		private var volumeSlide: int;
		private var portamentoSpeed: int;
		private var tonePortamentoSpeed: int = 0;
		private var tonePortamentoPeriod: int;
		private var vibratoSpeed: int;
		private var vibratoDepth: int;
		private var vibratoPosition: int;
		private var vibratoOffset: int;
		private var appegio: Appegio;
		
		//-- EXT EFFECT
		private var patternfirstRun: Boolean;
		private var patternfirstRunCount: int;
		private var patternfirstRunPosition: int;
		
		public function ModChannel( bitboy: BitBoy, id: int, pan: Number )
		{
			super( bitboy, id, pan );
		}
		
		public override function setMute( value: Boolean ): void
		{
			mute = value;
		}
		
		public override function reset(): void
		{
			wave = null;
			wavePhase = 0.0;
			repeatStart = 0;
			repeatLength = 0;
			firstRun = false;
			volume = 0;
			trigger = null;
			
			patternfirstRun = false;
			patternfirstRunCount = 0;
			patternfirstRunPosition = 0;
			
			volumeSlide = 0;
			portamentoSpeed = 0;
			tonePortamentoSpeed = 0;
			tonePortamentoPeriod = 0;
			vibratoSpeed = 0.0;
			vibratoDepth = 0.0;
			vibratoPosition = 0.0;
			vibratoOffset = 0.0;
			
			effect = 0;
			effectParam = 0;
		}
		
		public override function onTrigger( trigger: TriggerBase ): void
		{
			this.trigger = trigger;
			
			updateWave();
			
			if( trigger.effect == TONE_PORTAMENTO  )
			{
				initTonePortamento();
			}
			else if( trigger.period > 0 )
			{
				period = trigger.period;
				tone = TONE_TABLE.indexOf( period );
				tonePortamentoPeriod = period; // fix for 'delicate.mod'
				appegio = null;
			}
			
			initEffect();
		}
		
		public override function onTick( tick: int ): void
		{
			switch( effect )
			{
				case ARPEGGIO:
				
					updateApeggio( tick % 3 );
					break;
				
				case PORTAMENTO_UP:
				case PORTAMENTO_DN:
				
					updatePortamento();
					break;
				
				case TONE_PORTAMENTO:
				
					updateTonePortamento();
					break;
					
				case TONE_PORTAMENTO_VOLUME_SLIDE:
					
					updateTonePortamento();
					updateVolumeSlide();
					break;
				
				case VOLUME_SLIDE:
				
					updateVolumeSlide();
					break;
				
				case VIBRATO:
					
					updateVibrato();
					break;
				
				case VIBRATO_VOLUME_SLIDE:

					updateVibrato();
					updateVolumeSlide();
					break;
				
				case EXTENDED_EFFECTS:

					var extEffect: int = effectParam >> 4;
					var extParam: int = effectParam & 0xf;
				
					switch ( extEffect )
					{
						case 0x9: //-- retrigger note
							if ( tick % extParam == 0 )
								wavePhase = 0.0;
							break;
						
						case 0xc: //-- cut note
							wave = null;
							break;
					}

					break;
			}
		}
		
		public override function processAudioAdd( samples: Array ): void
		{
			if( wave == null || mute )
				return;
			
			var n: int = samples.length;
			
			if( n == 0 ) return;
			
			var sample: Sample;
			
			var len: int = wave.length;
			
			var volT: Number = ( volume / 64 ) * bitboy.parameterGain.getValue();
			var volL: Number = volT * ( 1 - pan ) / 2;
			var volR: Number = volT * ( pan + 1 ) / 2;
			
			var waveSpeed: Number = ( ( 7159090.5 / 2 ) / bitboy.getRate() ) / ( period + vibratoOffset ); // NTSC machine clock (Magic Number)
			
			var phaseInt: int;
			var alpha: Number;
			var amp: Number;
			
			for( var i: int = 0 ; i < n ; ++i )
			{
				if( firstRun )
				{
					if( wavePhase >= len ) // first run complete
					{
						if( repeatLength == 0 ) // stop channel
						{
							wave = null;
							return;
						}
						else
						{
							//-- truncate
							wave = wave.slice( repeatStart, repeatStart + repeatLength );
							len = wave.length;
							wavePhase %= len;
							firstRun = false;
						}
					}
				}
				else
					wavePhase %= len;
				
				//-- LINEAR INTERPOLATION
				//
				phaseInt = wavePhase;
				alpha = wavePhase - phaseInt;

				amp = wave[ phaseInt ] * ( 1 - alpha );
				if( ++phaseInt == len ) phaseInt = 0;
				amp += wave[ phaseInt ] * alpha;
				
				sample = samples[i];
				sample.left += amp * volL;
				sample.right += amp * volR;
				
				wavePhase += waveSpeed;
			}
		}

		private function initEffect(): void
		{
			if( trigger == null )
				return;

			effect = trigger.effect;
			effectParam = trigger.effectParam;

			if( effect != VIBRATO && effect != VIBRATO_VOLUME_SLIDE )
			{
				vibratoOffset = 0;
			}

			switch( effect )
			{
				case ARPEGGIO:
				
					if( effectParam > 0 )
					{
						initApeggio();
					}
					else
					{
						//-- no effect here, reset some values
						volumeSlide = 0;
					}
					break;
				
				case PORTAMENTO_UP:
				
					initPortamento( -effectParam );
					break;
				
				case PORTAMENTO_DN:
				
					initPortamento( effectParam );
					break;
					
				case TONE_PORTAMENTO:
					break;
				
				case VIBRATO:
					if ( ModTrigger( trigger ).modSample != null )
						volume = ModTrigger( trigger ).modSample.volume;
					initVibrato();
					break;

				case VIBRATO_VOLUME_SLIDE:

					/*This is a combination of Vibrato (4xy), and volume slide (Axy).
					The parameter does not affect the vibrato, only the volume.
					If no parameter use the vibrato parameters used for that channel.*/
					initVolumeSlide();
					break;
			
				case EXTENDED_EFFECTS:
				
					var extEffect: int = effectParam >> 4;
					var extParam: int = effectParam & 0xf;
				
					switch ( extEffect )
					{
						case 0x6: //-- pattern firstRun
							
								if( extParam == 0 )
								{
									patternfirstRunPosition = bitboy.getRowIndex() - 1;
								}
								else
								{
									if( !patternfirstRun )
									{
										patternfirstRunCount = extParam;
										patternfirstRun = true;
									}
									
									if( --patternfirstRunCount >= 0 )
									{
										bitboy.setRowIndex( patternfirstRunPosition );
									}
									else
									{
										patternfirstRun = false;
									}
								}
								
								break;
						
						case 0x9: //-- retrigger note

							wavePhase = .0;
							break;
						
						case 0xc: //-- cut note

							if( extParam == 0 )
								wave = null;
							break;
						
						default:
				
							trace( 'extended effect: ' + extEffect + ' is not defined.' );
							break;
					}

					break;
				
				case TONE_PORTAMENTO_VOLUME_SLIDE:
				case VOLUME_SLIDE:
				
					initVolumeSlide();
					break;
				
				case SET_VOLUME:
				
					volumeSlide = 0;
					volume = effectParam;
					break;

				case POSITION_JUMP:
				
					bitboy.patternJump( effectParam );
					break;

				case PATTERN_BREAK:

					bitboy.patternBreak( parseInt( effectParam.toString( 16 ) ) );
					break;

				case SET_SPEED:
				
					if( effectParam > 32 )
						bitboy.setBPM( effectParam );
					else
						bitboy.setSpeed( effectParam );
					break;
				
				default:
				
					trace( 'effect: ' + effect + ' is not defined.' );
					break;
			}
		}
		
		private function updateWave(): void
		{
			if( trigger == null )
				return;

			var modSample: ModSample = ModTrigger( trigger ).modSample;
			
			if( modSample == null || trigger.period <= 0 )
				return;

			wave = modSample.wave;
			wavePhase = 0.0;
			repeatStart = modSample.repeatStart;
			repeatLength = modSample.repeatLength;
			volume = modSample.volume;
			firstRun = true;
		}
		
		private function initApeggio(): void
		{
			appegio = new Appegio
			(
				period,
				TONE_TABLE[ tone + ( effectParam >> 4 ) ],
				TONE_TABLE[ tone + ( effectParam & 0xf ) ]
			);
		}
		
		private function updateApeggio( index: int ): void
		{
			if( effectParam > 0 )
			{
				if( index == 1 )
					period = appegio.p2;
				else if( index == 2 )
					period = appegio.p1;
			}
		}
		
		private function initVolumeSlide(): void
		{
			if( ModTrigger( trigger ).modSample )
				volume = ModTrigger( trigger ).modSample.volume;
			volumeSlide =  effectParam >> 4;
			volumeSlide -= effectParam & 0xf;
		}
		
		private function updateVolumeSlide(): void
		{
			var newVolume: int = volume + volumeSlide;

			if( newVolume < 0 ) newVolume = 0;
			else if( newVolume > 64 ) newVolume = 64;
			
			volume = newVolume;
		}
		
		private function initTonePortamento(): void
		{
			if( trigger.effectParam > 0 )
			{
				tonePortamentoSpeed = trigger.effectParam;
				if( trigger.period > 0 )
				{
					tonePortamentoPeriod = trigger.period;
				}
			}
		}
		
		private function updateTonePortamento(): void
		{
			if( period > tonePortamentoPeriod )
			{
				period -= tonePortamentoSpeed;
				if( period < tonePortamentoPeriod )
					period = tonePortamentoPeriod;
			}
			else if( period < tonePortamentoPeriod )
			{
				period += tonePortamentoSpeed;
				if( period > tonePortamentoPeriod )
					period = tonePortamentoPeriod;
			}
		}
		
		private function initPortamento( portamentoSpeed: int ): void
		{
			this.portamentoSpeed = portamentoSpeed;
		}
		
		private function updatePortamento(): void
		{
			period += portamentoSpeed;
		}
		
		private function initVibrato(): void
		{
			if( effectParam > 0 )
			{
				vibratoSpeed = effectParam >> 4;
				vibratoDepth = effectParam & 0xf;
				vibratoPosition = 0;
			}
		}
		
		private function updateVibrato(): void
		{
			vibratoPosition += vibratoSpeed;
			
			vibratoOffset = rint( SINE_TABLE[ vibratoPosition % SINE_TABLE.length ] * vibratoDepth / 128 );
		}

	}
		function rint( value: Number ): int
		{
			if( value > 0 )
				return value + .5;
			if( value < 0 )
				return -int( -value + .5 );
			else
				return 0;
		}
class Appegio
{
	public var p0: int;
	public var p1: int;
	public var p2: int;
	
	public function Appegio( p0: int, p1: int, p2: int )
	{
		this.p0 = p0;
		this.p1 = p1;
		this.p2 = p2;
	}
}

	import flash.utils.ByteArray;
	import flash.utils.Endian;
	
	class FormatFactory
	{
		public static function createFormat( file: ByteArray ): FormatBase 
		{
			var id: String;
			
			//-- ROUND #1
			//-- MOD
			
			file.endian = Endian.LITTLE_ENDIAN;
			file.position = 0x438;
			id = file.readMultiByte( 4, 'us-ascii' );
			
			if ( id.toLowerCase() == 'm.k.' )
				return new ModFormat( file );
			
			//-- ROUND #2
			//-- XM
			
			file.endian = Endian.LITTLE_ENDIAN;
			file.position = 0;
			id = file.readMultiByte( 17, 'us-ascii' );
			
			if ( id.toLowerCase() == 'extended module: ' )
				return new XMFormat( file );
				
			//-- ROUND #3
			//-- IT
			
			
			//-- ROUND #4
			//-- S3M
			
			
			//-- ROUND #5
			//-- NO SUCCESS			
			
			throw new Error( 'Unsupported format.' );
			
			return null;
		}
	}
	
	interface IAudioProcessor
	{
		function processAudio( samples: Array ): void;
		function reset(): void;
	}
	
	import flash.utils.getTimer;
	
	class BitBoy implements IAudioProcessor	{
		static private const RATIO: Number = 2.5;
		
		public const parameterGain: Parameter = new Parameter( new MappingNumberLinear( 0, 1 ), .75 );
		public const parameterMute: Parameter = new Parameter( new MappingBoolean(), false );
		public const parameterPause: Parameter = new Parameter( new MappingBoolean(), false );
		public const parameterChannel: Parameter = new Parameter( new MappingIntLinear( 0, 0xf ), 0xf );
		public const parameterLoopMode: Parameter = new Parameter( new MappingBoolean(), false );
				
		private var format: FormatBase;
		private var channels: Array;
		private var length: int;
		private var rate: Number;
		private var bpm: Number;
		private var speed: int;

		private var tick: int;
		private var rowIndex: int;
		private var patIndex: int;
		
		private var incrementPatIndex: Boolean;

		private var samplesPerTick: int;		
		private var rest: int = 0;
		
		private var complete: Boolean;
		private var lastRow: Boolean;
		private var idle: Boolean;
		private var loop: Boolean;
		
		/**
		 * Create a Bitboy instance
		 */
		public function BitBoy()
		{
		}
		
		/**
		 * Returns true is lastRow
		 */
		public function isIdle(): Boolean
		{
			return idle;
		}
		
		/**
		 * set the mod format
		 */
		public function setFormat( format: FormatBase ): void
		{
			this.format = format;
			
			init();
			
			length = computeLengthInSeconds();
			
			reset();
		}
		
		/**
		 * returns song length in seconds. returns -1 if the loop is looped
		 */
		public function getLengthSeconds(): int
		{
			return length;
		}
		
		/**
		 * process audio stream
		 * 
		 * param samples The samples Array to be filled
		 */
		public function processAudio( samples: Array ): void
		{
			if( complete )
			{
				idle = true;
				return;
			}
			
			var channel: ChannelBase;
			
			var samplesAvailable: int = samples.length;
			var sampleIndex: int = rest;
			var subset: Array;
			
			//-- process rest, if any
			if( rest > 0 )
			{
				subset = samples.slice( 0, rest );
				for each( channel in channels )
					channel.processAudioAdd( subset );

				samplesAvailable -= rest;
			}
			
			nextTick();
			
			//-- procees complete tick duration
			while( samplesAvailable >= samplesPerTick )
			{
				subset = samples.slice( sampleIndex, sampleIndex + samplesPerTick );

				for each( channel in channels )
					channel.processAudioAdd( subset );

				samplesAvailable -= samplesPerTick;
				sampleIndex += samplesPerTick;
				
				if( samplesAvailable > 0 )
					nextTick();
			}
			
			//-- procees remaining samples
			if( samplesAvailable > 0 )
			{
				subset = samples.slice( -samplesAvailable );
				for each( channel in channels )
					channel.processAudioAdd( subset );
			}
			
			rest = samplesPerTick - samplesAvailable;
		}
		
		public function reset(): void
		{
			rate = Audio.RATE44100;
			speed = format.defaultSpeed;
			tick = 0;

			setBPM( format.defaultBpm );

			rowIndex = 0;
			patIndex = 0;
			
			complete = false;
			lastRow = false;
			idle = false;
			loop = false;
			incrementPatIndex = false;
			
			for each( var channel: ChannelBase in channels )
				channel.reset();
		}
		
		public function setBPM( bpm: int ): void
		{
			samplesPerTick = rate * RATIO / bpm;
			
			this.bpm = bpm;
		}
		
		public function setSpeed( speed: int ): void
		{
			this.speed = speed;
		}
		
		public function setRowIndex( rowIndex: int ): void
		{
			this.rowIndex = rowIndex;
		}
		
		public function getRowIndex(): int
		{
			return rowIndex;
		}
		
		public function getRate(): Number
		{
			return rate;
		}
		
		public function patternJump( patIndex: int ): void
		{
			if( patIndex <= this.patIndex )
				loop = true;
			
			this.patIndex = patIndex;
			
			setRowIndex( 0 );
		}
		
		public function patternBreak( rowIndex: int ): void
		{
			setRowIndex( rowIndex );
			
			incrementPatIndex = true;
		}

		private function init(): void
		{
			channels = format.getChannels( this );
		}
		
		private function nextTick(): void
		{
			if( --tick <= 0 )
			{
				if( lastRow )
					complete = true;
				else
				{
					rowComplete();
					tick = speed;
				}
			}
			else
			{
				for each( var channel: ChannelBase in channels )
					channel.onTick( tick );
			}
		}
		
		private function rowComplete(): void
		{
			var channel: ChannelBase;
			//-- sync all parameter changes for smooth cuttings
			//
			
			if( !parameterPause.getValue() )
			{
				var mutes: int;
				
				if( parameterMute.getValue() )
					mutes = 0;
				else
					mutes = parameterChannel.getValue();
				
				for ( var i: int = 0; i < format.numChannels; ++i )
				{
					channel = channels[i];
					
					channel.setMute( ( mutes & ( 1 << i ) ) == 0 );
				}
				
				nextRow();
			}
			else
			{
				for each ( channel in channels )
					channel.setMute( true );
			}		
		}
		
		private function nextRow(): void
		{
			var channel: ChannelBase;
			var channelIndex: int;
			
			var currentPatIndex: int = patIndex;
			var currentRowIndex: int = rowIndex++;
			
			incrementPatIndex = false;
			
			for ( channelIndex = 0; channelIndex < format.numChannels; ++channelIndex )
			{
				channel = channels[ channelIndex ];
				channel.onTrigger( TriggerBase( format.getTriggerAt( format.getSequenceAt( currentPatIndex ), currentRowIndex, channelIndex ) ) );
			}
			
			if( incrementPatIndex )
			{
				nextPattern();
			}
			else if ( rowIndex == format.getPatternLength( format.getSequenceAt( currentPatIndex ) ) )
			{
				rowIndex = 0;
				nextPattern();
			}
		}
		
		private function nextPattern(): void
		{
			if( ++patIndex == format.length )
			{
				if( parameterLoopMode.getValue() )
					patIndex = format.restartPosition;
				else
					lastRow = true;
			}
		}
		
		private function computeLengthInSeconds(): int
		{
			reset();
			
			var channel: ChannelBase;
			var channelIndex: int;
			
			var currentPatIndex: int;
			var currentRowIndex: int;
			
			var samplesTotal: Number = 0;
			
			var ms: uint = getTimer();
			
			while( getTimer() - ms < 1000 ) // just be save
			{
				if( lastRow )
					break;

				currentPatIndex = patIndex;
				currentRowIndex = rowIndex++;
				incrementPatIndex = false;
				
				for ( channelIndex = 0; channelIndex < format.numChannels; ++channelIndex )				
				{
					channel = channels[ channelIndex ];
					channel.onTrigger( TriggerBase( format.getTriggerAt( format.getSequenceAt( currentPatIndex ), currentRowIndex, channelIndex ) ) );
				}
				
				if ( loop )
					return -1;
				
				if ( incrementPatIndex )
					nextPattern();
				
				if ( rowIndex == format.getPatternLength( format.getSequenceAt( currentPatIndex ) ) )
				{
					rowIndex = 0;
					nextPattern();
				}
				
				samplesTotal += samplesPerTick * speed;
			}
			
			return samplesTotal / rate;
		}
	}
	
// base64 code by 2ndyofyyx,
// http://wonderfl.kayac.com/code/b3a19884080f5ed34137e52e7c3032f3510ef861
import flash.utils.ByteArray; 
class Base64 extends ByteArray { 
    private static const BASE64:Array = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,62,0,0,0,63,52,53,54,55,56,57,58,59,60,61,0,0,0,0,0,0,0,0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,0,0,0,0,0,0,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,0,0,0,0,0]; 
    public function Base64(str:String) { 
        var n:int, j:int; 
        for (var i:int = 0; i < str.length && str.charAt(i) != "="; i++) { 
			if (str.charCodeAt(i) < 32) continue;
            j = (j << 6) | BASE64[str.charCodeAt(i)]; 
            n += 6; 
            while(n >= 8) { 
                writeByte((j >> (n -= 8)) & 0xFF); 
            } 
        } 
        position = 0; 
    } 
}

class SWF {
public static var bin:XML = <swf>RldTCSgCAAB4AATiAAAOpgAAGAEARBEJAAAARBDoAzwAQwKGnKdaCgEAAAAAAAAAAgBcMAIAAAAA
ANFPLZYMAQAAzgpTb3VuZFByb3ZpZGVyAAoOAQABAGVtcHR5AL8UpAEAAAEAAABmcmFtZTEAEAAu
AAAAAA8ADVNvdW5kUHJvdmlkZXINZmxhc2guZGlzcGxheQZTcHJpdGUFc291bmQJU291bmRJdGVt
C2ZsYXNoLm1lZGlhBVNvdW5kBk9iamVjdAxmbGFzaC5ldmVudHMPRXZlbnREaXNwYXRjaGVyDURp
c3BsYXlPYmplY3QRSW50ZXJhY3RpdmVPYmplY3QWRGlzcGxheU9iamVjdENvbnRhaW5lcggWARYD
GAIFABYHGAYWCgIBAQwHAQIHAgQHBAUHAQYHBQgHAQkHBwsHAgwHAg0HAg4JBgEGAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAIBAgkDAAEBAwAABAAEBQkGAAQAAAADAAICAQEEAQAFAQQEAAEGAAEB
CAkD0DBHAAABAQEJCgbQMNBJAEcAAAICAQEII9AwZQBgBjBgBzBgCDBgCTBgCjBgAjBgAlgAHR0d
HR0daAFHAAADAQEFBgPQMEcAAAQBAQYHBtAw0EkARwAABQIBAQUX0DBdC2AGMGAHMGAFMGAFWAEd
HR1oBEcAAB4TAgABAFNvdW5kSXRlbQAAAFNvdW5kUHJvdmlkZXIA</swf>;
}

class Mod {
public static var Snd:XML = <mod>YW5uYSBtdWxsZSBncmlpdHNpdCEyMDAwIHNhbWkgYWsAAAAAAAAAAAAAAAAAAAAAAABha2EgYmVh
dGhhd2sAAAAAAAAAAAAAA+MAOQJHAZkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAtsAQAAAAABzYW1p
LmFrQG1pbmRsZXNzLmNvbQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABl
bGV2YXRvcjk5IGNoaXBjb21wbwAAA2kAPAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABoZWkgdHVvbWFzLi4uZW4gbYQAAAAAAAkA
OAABAAhtdWlzdGFudSBsYWl0dGFhIHN1bGxlAAkAEAABAAhuaWl0IGdyaWl0c2VqhC4gOikAAAAA
ABIAJwABABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACQAQAABABAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAoAQAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIADQABABAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAEwAQAAHAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEgAQAADAEQAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEkAQAAEAEQAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAEkAQAAEAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEoAQAAGAEQAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAEsAQAAGAEQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEoAQAAFAEQA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAQAABABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAQAAB
ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIAQAABABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABIA
QAABABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkAQAAAAAgAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
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
t9zDuIGAioeAgICAgICAgICAhJOgw7jB7eHx/QQeHj9FY2NsbGxmf397f39/f39qVGlIWi4wHBP8
+fDr0KWomZCAgIuAgICQgIWZk5CrtMDZ4fH2FhYiMS5LVGB5YW95cH9/fn92f3NtcmBqdl1YVU83
GR8h//rh3MzApp+0hYCAgICAgICAgICAgICAgIKLnZ/Bw9LZ7e4AFS0rOj1gVF5sbGx/cG9/b3J8
eXNsZ2BeVUtOPDwxJyEWB/3t6+HG0Li0lpOAioCAgICAgICAgICAgIKAgISKgpmQrLK1w8/f2+33
CQoYJD9ASFRsbXZ1eX9/f39/f39/f3Z/fnh1b21tYV5PTElAOi0fGBgDDf3z8ejV1cTBxKydl46L
gICAgICAgICAgICAgICAgIKHhZqRn5+psbK6x83b6+v5BA8ZKzdCSFJjZ2x/f39/f39/f39/f39/
f39/f39/f3t5bGZgVE9FPzEtJx4WEAMA9Orn2dLNvsCuqaaflo2FgICAgICAgICAgICAgICAgICA
gICAgICAgIGOk5yir7fGw9Lc3ufz9wAJEhYlKzZDTlReZHJ5f39/f39/f39/f39/f39/f39/f39/
f39/eXVnZldRS0I5NCshGxUKBgDz6OLc2M2+vbivpp+Xk4iCgYCAgICAgICAgICAgICAgICAgICA
gICAgICAgICAgYWNlJylqa+4vsTM09vi6vD5AQwTGyIqMzpCSE9XW2Fnb3V7f39/f39/f39/f39/
f39/f39/f39/f39/f39/f3x1b2djWlRORj82MCceGA8HAPnx6uLb08zGvbixq6OfmZONiISAgICA
gICAgICAgICAgICAgICAgICAgICAgICAgICCh4uTmZ2iqa+1uMHEys3V2+Ll6/P3/QMJDxUbHycr
MDY8QkhMUlheZGdvcnZ7f39/f39/f39/f39/f39/f39/f39/f39/f398eHZwbWljYFtXT0xFQDo2
MCokHBgSCgT/9/Hr5N7V0MfBurSupqKalJGKhYGAgICAgICAgICAgICAgICAgICAgICAgICAgICA
gYeKkJSZn6OorrK4u8PGzNDV297k5+vu9Pb6/QEEBwoMDxIVGBseIiUoLTM2OT9FSU5SVVpeYWZp
bXJzeHt8f39/f39/f39/f39/fHt2c21qZmNhW1dUUU5JRkM/PTk2MzArJyQhHBkVEAwHBAD89/Pw
6uXh3tnW0M3HxMC7t7KvqaWgnZeWkI2Ih4KAgICAgICAgICAgICAgICAgICAgYKHio6RlJqdoqap
rrG0uLu+wcbJzNDT1tne4eTn6+709/n/AQYJDBASFhsfIicqLjE0OTo9QENGSU5OUlRVV1tbXmFh
Y2RmZ2lpbGxsbGxsampqZ2lkY2FgXVpYVVRRTkxIRkI/PDk2MzAtKickIR8cGRYVEA8MCQQD//r5
9PHt6uXi3NnV0MzJxsC9urWyrqyoqKOioJ+dnJqZl5eWlpSTkZGQkI6NjY2NjY6OkJGRlJaXmZqd
n6Kjpqusr7S3ur7Dx8rP09jc4uXq7vP2+v0BBAkMDxIVFhkbHiEiJCUoKisrLjEzNDY3Ojo8PT9A
QENDRUVFRUZGRUhGRkZFRUVFQ0NDQkJAQEBAQD09PDw8OTk3Nzc2NDMxMTAuLS0rKignJyQiHx4b
GBUTEA4KCAUCAP/8+vj18/Lw7u7s6+ro6Ojm5+Xk4+Tj4+Tj4+Tk5OXl5ufo6Onq6+vt7e/v8PHy
8/T19vj5+vv9AAAAAAAA8f4ru00GhX/Jxjq7JjAgj3+Af+CAf4CLf4AMcIAmZ/eFf4BsgHeAPAcV
GlCA6X+AF/wnEA6b7H+AIROmJaER1hEghYB/zZx/0IB/2IB/f4B/3JfjeBCXeBItDD+QZyQdvyJ+
gH+ATgKZf4AEN8xcsv8cmljmf4B/wgEMUKsevUzYBsrKOgMOxTC5/SLv4z4Xy36A7n2kWNcOtn+4
HPRuy5J/ywL4BX+AG1G0AzbmiAFvgCN0gEvNYKDlf4B/Adgm5AOif4Bv/ESUfbITHIVNtVS1ZqJU
6v7LRPQp6Lyt/X+Af48kF+Pqf5Gy8H+Af7IFHwuA2XqNf41/gjpX+cwSc46if4B/+4B/D4N/9+n7
UbiFNjv/3It/LNvu7QkOxCmQa9mNf74N2X/UgHYnvBsp1KET9R3/iH9LgFbAJPcE4lmnRPf58x7k
IweAU7gE6iLgBTWYeJHrxXSAOdR/ogz0CpVHyUWAf/0+rGCAOQL75hj54wEu8q493SeAPSdQgDMH
Lw1aliTXbp35gH/n0i7ukfdR+RxUgH+ASNuffwAFtzOSf6IOwz7kXQyCRQqPf4AXboDcdIA6apfn
DyS9S+sNgH+mf5yYf4AaGvf9ABDlRIAfVjCAf9OrP3eAf4Am4xX5AvDOPODIf6bqG0yvdsauf4B/
24sLQuDu/ddUzCPs9wn+FiaCL16sGVuDexY3pKt/tMJ/oYkq1F6OMVcbr+ct8YB/VoB/pzPwA1aU
AxIM+iL6sjw+r12iMf/w6Rn3zDEwlJR/pbt/sdELCzfSgH+gFxzktVc8gH+AKwXs8wLHQN8jDhqy
f9iOM9/oM6w/BhvzBXGA41qOf4AyOIB5xmHygEQ/hHjSs3+0DwEDtX+AGHmSoXuJwH+Af1WF7zWA
efgkgGeKf4DW9zbkeYB61XqAf9GnbVivPPKzBHqATPIAJhcGiUyTf8OAXzuA13+AfySjDsc8ikvy
CbBjh0ntYAm+HDqGRu4Bnn+Af4l08oZ/gB/l43iAHBMP/bJ/s6piEtShJBQ8kn/CyH+AK/uef9Pa
6ewgZ5MlGsV/gB1VgiRulr5Z39om8g6CK+Y+z6pqNpwM4M5/xeJwttJ/iyUmE9B+iuZM7gL75mOw
43+SP+Qa7feVcbh/rRPOcq4T/u4SoiwPgF+mf6LvdtDt+SDm6kz2y9N9xwLReoB81QPhHFXwgFqn
f6V3gDoNrjWmQu4Be9Xi8ETrUa+/XhANGqf+KQTuBKI49SuAcCeAf7aRctRIgHqHaoB/uiji+UnE
ED6/GfzqUIBZMbP+MbbGVZv5J1qAf6AJxUA29bsKdYAzWK71Pt52gEFTgBtvwRTiZKUhuD+Ff4Bu
rGDl8g4G/zUK/vXJNKk6LIB52QQAKYBji1CweKg7vn2h/zfUT4RG+t7kf4Bw9ustAQYAqvIe/f8P
hVXrLtn91A4DAAD/qlLhAz/CEjvpwu0zAPIBAeFd6/b9Av9Bvxna85tv3h3hGQEtzgAU0CASiF8Z
z/F6jgUW9QKQaTi11jf2uXymHuUMBvfjId0WolO+Zu7lCQH/DPOhXpp+DMEm3w3/4S2hcMA728YZ
X7gk60OSQfcIgH/fBjDND/8A/wD/G/fpIO7v8XiaIPzARubE+iXrON8U7XiRGs4w7PYKGtL/1XOq
FRzjwj/2uB1Jufs/4DjNVJoh7gj/vGWRMhkO6tdqt+cm88H6R//iCv4b68gXWaka/cD8RA7y9wYh
4gY/gBN2ohr6AAj9Hw6sT6s0sjfVE8J4qij2CNkX1iL0GBW6Z7wRNM8M//AdoUa2OQAJ6ya9Y70P
/8wpEuQIxjLxAf8A/wv+AOEcFf/0Ah7jEfriC+8wsFPJ/Q368RP0Egbz5zva6zvlA+IdM4AzHMgg
IfrwBCzo+uAe9/H4AQT+//8ewWna9yjpB/4AvX+dIvkABRPoyEDn1icW4esbBxXlBQDhHLU38QAA
/wA46vcDHbMx9AHx7yniCP4AHtQT/QDWIPf/HuNbzS2xHvgg5wPQYb0RPpEgTL8S/QDhHOUXtRo5
zwz//wD//wDhUscRAiH78DDr/v8P8vbVLvEB/wD/AP8AFL8u9f8eoEb27irhBj+v/gvoIAYZ3Qb/
AP8q2eAn/gjz5T6/EzvGEOBG0c5a6dw5yRT9/yPf1C8E8gIW7egrCOnbJgcC0wwD5BQ5k0D5E+jp
DgrzAh7C4jIQ8APwCP/MVsr/O/XuBQD/AC3U6f421wq/NgD0Ah7u5RX8//AIHeMGDhTz3xn7/wDW
IPf9/wD/IO4C/wD/AN8DGu4B/xL8/Pj/AQP9+gf9Bf4G+v0N/P38Av8A/QEAvbu4trOysbCuBwcG
BQQCAQD/vbu4trOysbCuBwcGBQQCAQD/rq2rqqmop6enp6ipq7CztbjU7fj8AQIEBQcMDQ4PERIU
FhYXvb27u7i4trazs7KysbGwsK6uBwcHBwYGBQUEBAICAQEAAP//vb27u7i4trazs7KysbGwsK6u
BwcHBwYGBQUEBAICAQEAAP//ysXDwsLCwsPy9ff6/QElJicpysWin5yalZSSkI6NjY2OkJCQkNzj
6/H4+vz/AQQHCg0QEhQWGBowMDc3MDA3Nz8/R09ed319fX19fX19fX19fX19fX19b1g/HgAA8M+4
sKGQkIiAiJCQobDAwNnwAAAAFx4wNz8/R09YWFhYT1hPPz8/NzcwHhceFw8HDw8HBwcPDw8PFxce
JiYmMDA3Nzc/Pz8/Pz8/Pz8/Pzc/PzcwMDA3JjAwJjAmMDAmMDAmJiYwMDAmMDA3PT9HT15vfTc3
Pz83Nzc/Nzc3fX19D319AG99N7B9fYAwfafAbwCAMBeQ4SbIwCYAuAAXyPgeAOEXB+gHFwAAHgcA
Hg8ADxcPBxceBx4XDxceHhceHhceJh4eJh4eJiYmJiYmHiYwJiYwMCYmMDAmMDcmJjAmMCYwMDAw
MDAwMDAwNzc3NzAwNzAwNzA3NzA3Nzc3MDAwMDAwMDAwJiYwJiYwMDBHd319fX19fX19fX1YAKeA
gICAgKHhDzdob29eRx4A4cCwoaGnyOEADyY/P0c/MCYPAADw6PD4AAAPHiYwNzc3JiYeDwcAAAAH
Bw8XHh4mNzAwJjAeHh4PDw8PFx4eHh4wMDAwJjAeHh4eHh4eHh4eJh4eHjAmJiYmHh4eJhceHh4m
Hh4eLjc3NDA0MDQuNzcuN0deXlpeWl5aWlpaNwvf29vb3+YLJmp9fUVxXgMoG+ObAQmfCz3fAF4s
5jcez/AL08sD9NsAG/wbLiILIhcACwsAAA8HBxsbFyImIh4iIg8bFxMXGxsbIiYiJi4qKioiIiYm
Ih4iIh4iMComMDAqJjAwIiYuJiYqKjAqMDAwMDAwKiowKi4yMjI0NDQwNDQwLjAwJi5DUVFRVlFY
UVFRVjQA19fX29vjByZHXl5iWk8/LhsFAQEJHjI9SV5mamZeUUU3JA/88PT45urw8Obq39fX08/P
19/f8AALExcmKjQ3NzIuMjc3NzcyOzk3Nzc3NzQmHiIeGxMTFxMTEx4eGx4iIiIqKiImKi4uLjc3
Nzc3Nzc3NzIyNDIyLCgqKio0NDIwMjAyKjQ0KjdPbm5sbmxubGxsbDfyrq2tra6+9h5gfX1gbE8F
A+i4kcTPrvoe+h5WQRs7Iu74/tnR8u7sABwVLDY0ICgeCw0FAAAHBwsZICIsLiwqKCQTGRcVFxkZ
HCQqKCoyMDAsJCQmJiAbICAeIDAsJjAwLCYwMCAiKiYmKCwwLDAwMDAwMCgoMCgqLi4uNDQyNzQ0
Nzs0NDdaXl4qZm47dX1aF319/lZ9ER51Pf5WSQUoPwPuEwDT5ua8y9fItMvIvMvb19/u8PgPBwAT
Gx4eKi4mNjc0Nzs2NzYuKi4uKiYiGx4eGxcbGxcTFx4bGx4iHiIqKiYwNC4uNDI3Mjc3Nzc3Nzc3
NDc7NzQ0MDQuMDAuMC40MC40NC4qKjAwMK6tq6qpqKenp6eoqauws7W41O34/AECBAUHDA0ODxES
FBYWF66tq6qpqKenp6eoqauws7UsKu34/AECBAUHDA0ODxESFBYWF66tq6qpqKenp6eoqauws7W4
1O34/AGpqakHDA0ODxESFBYWF66tq6qpqFBOp6eoqauws7W41O34/AGdnZ+fDA0ODxESFBYWF66t
qqinp6mwtdTt/AIFDA4RFA==</mod>;
}