8Bitboy Mod Playerを作ってみましたが不具合?
今度は8bitboy mod playerを作ってみましたが
* 再生してみるとプツプツとスムーズに再生されません。
* 前回は一気に変換していましたが、今回は少しずつ
* 変換再生しているので処理と同期に問題があるのかな?
* デバックしてくれる人を求め、とりあえずアップしました。
* http://8bitboy.popforge.de/
/**
* 今度は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>;
}