<|start_filename|>micromod/compiler/Pattern.java<|end_filename|>
package micromod.compiler;

public class Pattern implements Element {
	private Module parent;
	private Row child = new Row( this );
	private int patternIdx;

	public Pattern( Module parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "Pattern";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return child;
	}
	
	public void begin( String value ) {
		patternIdx = Parser.parseInteger( value );
		child.setRowIdx( 0 );
	}
	
	public void end() {
	}

	public String description() {
		return "\"Index\" (Pattern index, from 0 to 127.)";
	}

	public void setNote( int rowIdx, int channelIdx, micromod.Note note ) {
		parent.getPattern( patternIdx ).setNote( rowIdx, channelIdx, note );
	}

	public int getPatternIdx() {
		return patternIdx;
	}
}


<|start_filename|>micromod/compiler/Row.java<|end_filename|>
package micromod.compiler;

public class Row implements Element {
	private int rowIdx;
	private Pattern parent;

	public Row( Pattern parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "Row";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String row ) {
		String[] notes = Parser.split( row, ' ' );
		micromod.Note output = new micromod.Note();
		int noteIdx = 0;
		if( notes[ noteIdx ].length() < 4 ) {
			int idx = Parser.parseInteger( notes[ noteIdx++ ] );
			if( idx < rowIdx ) {
				throw new IllegalArgumentException( "Row index is less less than current (" + rowIdx + "): " + idx );
			}
			rowIdx = idx;
		}
		int chanIdx = 0;
		while( noteIdx < notes.length ) {
			try {
				output.fromString( notes[ noteIdx++ ] );
				parent.setNote( rowIdx, chanIdx, output );
				chanIdx++;
			} catch( IllegalArgumentException e ) {
				String msg = "At Pattern " + parent.getPatternIdx() + " Row " + rowIdx + " Channel " + chanIdx;
				throw new IllegalArgumentException( msg + ": " + e.getMessage() );
			}
		}
		rowIdx++;
	}
	
	public void end() {
	}

	public String description() {
		return "\"00 C-2-1--- --------\" (Specify a single row within a pattern.)\n" +
			"(Row index, from 0 to 63, followed by notes for each channel.)\n" +
			"(See the '-notes' command line option for more information.)";
	}
	
	public void setRowIdx( int rowIdx ) {
		this.rowIdx = rowIdx;
	}	
}


<|start_filename|>micromod/compiler/Scale.java<|end_filename|>

package micromod.compiler;

public class Scale implements Element {
	private Macro parent;
	private Root sibling;
	
	public Scale( Macro parent ) {
		this.parent = parent;
		sibling = new Root( parent );
	}
	
	public String getToken() {
		return "Scale";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setScale( value );
	}
	
	public void end() {
	}

	public String description() {
		return "\"C#D#EF#G#A#B\" (Key signature for Macro transpose.)\n" +
			"(All scales are specified as 12 notes starting from C.)\n" +
			"(C-Major would be 'C-D-EF-G-A-B', and C-Minor 'C-D#-F-G#-#-'.)";
	}
}


<|start_filename|>micromod/compiler/LoopStart.java<|end_filename|>
package micromod.compiler;

public class LoopStart implements Element {
	private Instrument parent;
	private LoopLength sibling;

	public LoopStart( Instrument parent ) {
		this.parent = parent;
		sibling = new LoopLength( parent );
	}
	
	public String getToken() {
		return "LoopStart";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setLoopStart( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"Offset\" (Offset of sustain phase in samples.)";
	}
}


<|start_filename|>micromod/compiler/Chorus.java<|end_filename|>
package micromod.compiler;

public class Chorus implements Element {
	private Waveform parent;
	private Point sibling;
	private int cycles, modRate, lfoRate, detune, mix;

	public Chorus( Waveform parent ) {
		this.parent = parent;
		sibling = new Point( parent );
	}
	
	public String getToken() {
		return "Chorus";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		int[] values = Parser.parseIntegerArray( value );
		if( values.length < 1 || values.length > 5 ) {
			throw new IllegalArgumentException( "Invalid Chorus parameter (Cycles[,ModRate,LfoRate,Detune,Mix]): " + value );
		}
		cycles = values[ 0 ];
		modRate = values.length > 1 ? values[ 1 ] : 1;
		lfoRate = values.length > 2 ? values[ 2 ] : 0;	
		detune = values.length > 3 ? values[ 3 ] : 0;
		mix = values.length > 4 ? values[ 4 ] : 128;
	}
	
	public void end() {
		parent.setModulation( cycles, modRate, lfoRate, detune, mix );
	}

	public String description() {
		return "\"Cycles[,ModRate,LfoRate,Detune,Mix]\" (Phase modulation.)\n" +
			"(Cycles controls cycles of unmodulated oscillator 1.)\n" +
			"(ModRate controls cycles of osc 2 modulation. Default 1.)\n" +
			"(LfoRate controls cycles of modulator LFO. Default 0.)\n" +
			"(Detune adjusts pitch of osc 2 in eighths of a semitone.)\n" +
			"(Mix controls oscillator mix from 0 to 256. Default 128.)";
	}
}


<|start_filename|>micromod/compiler/Module.java<|end_filename|>

package micromod.compiler;

public class Module implements Element {
	private micromod.Module module;
	private micromod.Macro[] macros = new micromod.Macro[ 100 ];
	private Channels child = new Channels( this );
	private java.io.File resourceDir;

	public Module( java.io.File resourceDir ) {
		this.resourceDir = resourceDir;
	}

	public String getToken() {
		return "Module";
	}
	
	public Element getParent() {
		return null;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return child;
	}
	
	public void begin( String value ) {
		module = new micromod.Module();
		module.setSongName( value );
	}
	
	public void end() {
		/* Expand macros.*/
		int speed = 6;
		int numChannels = module.getNumChannels();
		micromod.Note note = new micromod.Note();
		micromod.Pattern[] patterns = new micromod.Pattern[ module.getSequenceLength() ];
		for( int sequenceIdx = 0; sequenceIdx < patterns.length; sequenceIdx++ ) {
			micromod.Pattern pattern = module.getPattern( module.getSequenceEntry( sequenceIdx ) );
			patterns[ sequenceIdx ] = new micromod.Pattern( numChannels, pattern );
		}
		for( int patternIdx = 0; patternIdx < patterns.length; patternIdx++ ) {
			micromod.Pattern pattern = patterns[ patternIdx ];
			for( int rowIdx = 0; rowIdx < micromod.Pattern.NUM_ROWS; rowIdx++ ) {
				for( int channelIdx = 0; channelIdx < numChannels; channelIdx++ ) {
					pattern.getNote( rowIdx, channelIdx, note );
					if( note.effect == 0xF && note.parameter < 0x20 ) {
						speed = note.parameter;
					}
				}
				for( int channelIdx = 0; channelIdx < numChannels; channelIdx++ ) {
					pattern.getNote( rowIdx, channelIdx, note );
					micromod.Macro macro = macros[ note.instrument ];
					if( macro != null ) {
						note.instrument = 0;
						pattern.setNote( rowIdx, channelIdx, note );
						macro.expand( module, patterns, patternIdx, rowIdx, channelIdx, speed );
						pattern.getNote( rowIdx, channelIdx, note );
					}
					module.getPattern( module.getSequenceEntry( patternIdx ) ).setNote( rowIdx, channelIdx, note );
				}
			}
		}
		System.out.println( getToken() + " end." );
	}

	public String description() {
		return "\"Title\" (Song title, maximum 20 characters.)";
	}

	public void setNumChannels( int numChannels ) {
		module.setNumChannels( numChannels );
	}

	public void setSequenceLength( int length ) {
		module.setSequenceLength( length );
	}

	public void setSequenceEntry( int seqIdx, int patIdx ) {
		module.setSequenceEntry( seqIdx, patIdx );
	}
	
	public micromod.Instrument getInstrument( int insIdx ) {
		return module.getInstrument( insIdx );
	}
	
	public void setMacro( int macroIdx, micromod.Macro macro ) {
		if( macroIdx > 0 && macroIdx < 100 ) macros[ macroIdx ] = macro;
	}
	
	public micromod.Pattern getPattern( int patIdx ) {
		return module.getPattern( patIdx );
	}
	
	public micromod.Module getModule() {
		return module;
	}
	
	public java.io.InputStream getInputStream( String path ) throws java.io.IOException {
		return new java.io.FileInputStream( new java.io.File( resourceDir, path ) );
	}
}


<|start_filename|>micromod/compiler/Root.java<|end_filename|>

package micromod.compiler;

public class Root implements Element {
	private Macro parent;
	private Note sibling;
	
	public Root( Macro parent ) {
		this.parent = parent;
		sibling = new Note( parent );
	}
	
	public String getToken() {
		return "Root";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setRoot( value );
	}
	
	public void end() {
	}

	public String description() {
		return "\"C-2\" (No transpose will be applied when played at this key.)";
	}
}


<|start_filename|>micromod/Module.java<|end_filename|>

package micromod;

public class Module {
	public static final int C2_PAL = 8287, C2_NTSC = 8363;

	private String songName;
	private int sequenceLength, restartPos, c2Rate, gain;
	private byte[] sequence;
	private Pattern[] patterns;
	private Instrument[] instruments;

	public Module() {
		setSongName( "Untitled" );
		sequence = new byte[ 128 ];
		patterns = new Pattern[ 128 ];
		setSequenceLength( 1 );
		setNumChannels( 4 );
		instruments = new Instrument[ 32 ];
		for( int instIdx = 1; instIdx < instruments.length; instIdx++ ) {
			instruments[ instIdx ] = new Instrument();
		}
	}

	public Module( java.io.InputStream inputStream ) throws java.io.IOException {
		this();
		load( inputStream );
	}

	public Module( byte[] module ) {
		this();
		load( module );
	}

	public String getSongName() {
		return songName;
	}
	
	public void setSongName( String name ) {
		if( name == null ) {
			songName = "";
		} else if( name.length() > 20 ) {
			songName = name.substring( 0, 20 );
		} else {
			songName = name;
		}
	}

	public int getNumChannels() {
		return patterns[ 0 ].getNumChannels();
	}

	public void setNumChannels( int numChannels ) {
		if( numChannels < 1 || numChannels > 32 ) {
			throw new IllegalArgumentException( "Number of channels out of range (1 to 32): " + numChannels );
		}
		setC2Rate( numChannels == 4 ? C2_PAL : C2_NTSC );
		setGain( numChannels == 4 ? 64 : 32 );
		for( int patIdx = 0; patIdx < patterns.length; patIdx++ ) {
			Pattern pattern = patterns[ patIdx ];
			if( pattern != null ) {
				patterns[ patIdx ] = new Pattern( numChannels, pattern );
			}
		}
		if( patterns[ 0 ] == null ) {
			patterns[ 0 ] = new Pattern( numChannels );
		}
	}

	public int getSequenceLength() {
		return sequenceLength;
	}
	
	public void setSequenceLength( int sequenceLength ) {
		if( sequenceLength < 1 || sequenceLength > 128 ) {
			throw new IllegalArgumentException( "Song length out of range (1 to 128): " + sequenceLength );
		}
		this.sequenceLength = sequenceLength;
	}
	
	public int getC2Rate() {
		return c2Rate;
	}
	
	public void setC2Rate( int c2Rate ) {
		if( c2Rate < 1 || c2Rate > 65536 ) {
			throw new IllegalArgumentException( "C2Rate out of range (1 to 65536): " + c2Rate );
		}
		this.c2Rate = c2Rate;
	}
	
	public int getGain() {
		return gain;
	}
	
	public void setGain( int gain ) {
		if( gain < 0 || gain > 512 ) {
			throw new IllegalArgumentException( "Gain out of range (0 to 512): " + gain );
		}
		this.gain = gain;
	}

	public int getSequenceEntry( int seqIdx ) {
		if( seqIdx < 0 || seqIdx > 127 ) {
			throw new IllegalArgumentException( "Sequence index out of range (0 to 127): " + seqIdx );
		}
		return sequence[ seqIdx ];
	}
	
	public void setSequenceEntry( int seqIdx, int patIdx ) {
		getSequenceEntry( seqIdx );
		if( patIdx < 0 || patIdx > 127 ) {
			throw new IllegalArgumentException( "Pattern index out of range (0 to 127): " + patIdx );
		}
		sequence[ seqIdx ] = ( byte ) patIdx;
	}
	
	public Pattern getPattern( int patIdx ) {
		if( patIdx < 0 || patIdx > 127 ) {
			throw new IllegalArgumentException( "Pattern index out of range (0 to 127): " + patIdx );
		}
		if( patterns[ patIdx ] == null ) {
			patterns[ patIdx ] = new Pattern( getNumChannels() );
		}
		return patterns[ patIdx ];
	}
	
	public Instrument getInstrument( int instIdx ) {
		if( instIdx < 1 || instIdx > 31 ) {
			throw new IllegalArgumentException( "Instrument index out of range (1 to 31): " + instIdx );
		}
		return instruments[ instIdx ];
	}

	public static int calculateModuleLength( byte[] moduleHeader ) {
		int moduleLength = 1084 + 4 * calculateNumChannels( moduleHeader ) * 64 * calculateNumPatterns( moduleHeader );
		for( int instIdx = 1; instIdx < 32; instIdx++ ) {
			moduleLength += Instrument.calculateSampleDataLength( moduleHeader, instIdx );
		}
		return moduleLength;
	}

	public int load( byte[] module ) {
		char[] name = new char[ 20 ];
		for( int idx = 0; idx < name.length; idx++ ) {
			int chr = module[ idx ] & 0xFF;
			name[ idx ] = chr > 32 ? ( char ) chr : 32;
		}
		setSongName( new String( name ) );
		setSequenceLength( module[ 950 ] & 0x7F );
		restartPos = module[ 951 ] & 0x7F;
		if( restartPos >= sequenceLength ) {
			restartPos = 0;
		}
		for( int seqIdx = 0; seqIdx < 128; seqIdx++ ) {
			setSequenceEntry( seqIdx, module[ 952 + seqIdx ] & 0x7F );
		}
		setNumChannels( calculateNumChannels( module ) );
		int numPatterns = calculateNumPatterns( module );
		int moduleOffset = 1084;
		for( int patIdx = 0; patIdx < numPatterns; patIdx++ ) {
			moduleOffset = getPattern( patIdx ).load( module, moduleOffset );
		}
		for( int instIdx = 1; instIdx <= 31; instIdx++ ) {
			moduleOffset = getInstrument( instIdx ).load( module, instIdx, moduleOffset );
		}
		return moduleOffset;
	}

	public int load( java.io.InputStream inputStream ) throws java.io.IOException {
		byte[] header = new byte[ 1084 ];
		readFully( inputStream, header, 0, header.length );
		byte[] module = new byte[ calculateModuleLength( header ) ];
		System.arraycopy( header, 0, module, 0, header.length );
		readFully( inputStream, module, header.length, module.length - header.length );
		return load( module );
	}

	public byte[] save() {
		byte[] module = new byte[ save( null ) ];
		save( module );
		return module;
	}
	
	public int save( byte[] outBuf ) {
		if( outBuf != null ) {
			writeAscii( songName, outBuf, 0, 20 );
			outBuf[ 950 ] = ( byte ) sequenceLength;
			outBuf[ 951 ] = ( byte ) ( restartPos & 0x7F );
			int numChannels = getNumChannels();
			if( numChannels == 4 ) {
				writeAscii( "M.K.", outBuf, 1080, 4 );
			} else if( numChannels < 10 ) {
				outBuf[ 1080 ] = ( byte ) ( '0' + numChannels );
				writeAscii( "CHN", outBuf, 1081, 3 );
			} else {
				outBuf[ 1080 ] = ( byte ) ( '0' + numChannels / 10 );
				outBuf[ 1081 ] = ( byte ) ( '0' + numChannels % 10 );
				writeAscii( "CH", outBuf, 1082, 2 );
			}
		}
		int numPatterns = 0;
		for( int seqIdx = 0; seqIdx < 128; seqIdx++ ) {
			int patIdx = seqIdx < sequenceLength ? getSequenceEntry( seqIdx ) : 0;
			if( outBuf != null ) {
				outBuf[ 952 + seqIdx ] = ( byte ) patIdx;
			}
			if( patIdx >= numPatterns ) {
				numPatterns = patIdx + 1;
			}
		}
		int outIdx = 1084;
		for( int patIdx = 0; patIdx < numPatterns; patIdx++ ) {
			outIdx = patterns[ patIdx ].save( outBuf, outIdx );
		}
		for( int instIdx = 1; instIdx <= 31; instIdx++ ) {
			outIdx = instruments[ instIdx ].save( outBuf, instIdx, outIdx );
		}
		return outIdx;
	}

	private static void writeAscii( String text, byte[] outBuf, int offset, int len ) {
		if( text == null ) {
			text = "";
		}
		for( int idx = 0; idx < len; idx++ ) {
			outBuf[ offset + idx ] = ( byte ) ( idx < text.length() ? text.charAt( idx ) : 32 );
		}
	}

	private static int calculateNumChannels( byte[] header ) {
		switch( ( ( header[ 1082 ] & 0xFF ) << 8  ) | ( header[ 1083 ] & 0xFF ) ) {
			case 0x4b2e: /* M.K. */
			case 0x4b21: /* M!K! */
			case 0x5434: /* FLT4 */
				return 4;
			case 0x484e: /* xCHN */
				return header[ 1080 ] - '0';
			case 0x4348: /* xxCH */
				return ( header[ 1080 ] - '0' ) * 10 + header[ 1081 ] - '0';
			default:
				throw new IllegalArgumentException( "MOD Format not recognised!" );
		}
	}

	private static int calculateNumPatterns( byte[] header ) {
		int numPatterns = 0;
		for( int seqIdx = 0; seqIdx < 128; seqIdx++ ) {
			int patIdx = header[ 952 + seqIdx ] & 0x7F;
			if( patIdx >= numPatterns ) {
				numPatterns = patIdx + 1;
			}
		}
		return numPatterns;
	}

	private static void readFully( java.io.InputStream inputStream, byte[] buffer, int offset, int length ) throws java.io.IOException {
		int read = 1, end = offset + length;
		while( read > 0 ) {
			read = inputStream.read( buffer, offset, end - offset );
			offset += read;
		}
	}
}


<|start_filename|>micromod/compiler/Octave.java<|end_filename|>
package micromod.compiler;

public class Octave implements Element {
	private Waveform parent;
	private Chorus sibling;

	public Octave( Waveform parent ) {
		this.parent = parent;
		sibling = new Chorus( parent );
	}
	
	public String getToken() {
		return "Octave";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setOctave( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"0\" (Waveform octave, from -4 to 4.)";
	}
}


<|start_filename|>micromod/compiler/Instrument.java<|end_filename|>

package micromod.compiler;

public class Instrument implements Element {
	private micromod.Instrument instrument;
	private int instrumentIndex, loopStart, loopLength;
	private boolean pingPong;
	private AudioData audioData;
	private Module parent;
	private Macro sibling;
	private Name child = new Name( this );
	
	public Instrument( Module parent ) {
		this.parent = parent;
		sibling = new Macro( parent );
	}
	
	public String getToken() {
		return "Instrument";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return child;
	}
	
	public void begin( String value ) {
		instrumentIndex = Parser.parseInteger( value );
		instrument = parent.getInstrument( instrumentIndex );
		loopStart = loopLength = 0;
		pingPong = false;
		audioData = null;
	}
	
	public void end() {
		if( audioData != null ) {
			instrument.setSampleData( audioData.quantize(), loopStart, loopLength, pingPong );
		}
		System.out.println( "Instrument " + instrumentIndex +
			": Volume " + instrument.getVolume() +
			", FineTune " + instrument.getFineTune() +
			", LoopStart " + instrument.getLoopStart() +
			", SampleEnd " + ( instrument.getLoopStart() + instrument.getLoopLength() ) );
	}

	public String description() {
		return "\"Index\" (Instrument index, from 1 to 31.)";
	}

	public void setName( String name ) {
		instrument.setName( name );
	}
	
	public void setVolume( int volume ) {
		instrument.setVolume( volume );
	}
	
	public void setFineTune( int fineTune ) {
		instrument.setFineTune( fineTune );
	}
	
	public AudioData getAudioData() {
		return audioData;
	}
	
	public void setAudioData( AudioData audioData ) {
		this.audioData = audioData;
	}
	
	public void setLoopStart( int loopStart ) {
		this.loopStart = loopStart;
		setLoopLength( audioData.getLength() - loopStart );
	}

	public void setLoopLength( int loopLength ) {
		this.loopLength = loopLength;
	}

	public void setPingPong( boolean enabled ) {
		this.pingPong = enabled;
	}

	public java.io.InputStream getInputStream( String path ) throws java.io.IOException {
		return parent.getInputStream( path );
	}
}


<|start_filename|>micromod/compiler/Volume.java<|end_filename|>
package micromod.compiler;

public class Volume implements Element {
	private Instrument parent;
	private FineTune sibling;

	public Volume( Instrument parent ) {
		this.parent = parent;
		sibling = new FineTune( parent );
	}
	
	public String getToken() {
		return "Volume";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setVolume( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"0\" (Instrument volume, 0 to 64.)";
	}
}


<|start_filename|>micromod/compiler/Crop.java<|end_filename|>

package micromod.compiler;

public class Crop implements Element {
	private WaveFile parent;
	private Gain sibling;

	public Crop( WaveFile parent ) {
		this.parent = parent;
		sibling = new Gain( parent );
	}
	
	public String getToken() {
		return "Crop";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		int[] params = Parser.parseIntegerArray( value );
		int offset = 0, count = 0, divisions = 0;
		if( params.length == 1 ) {
			count = params[ 0 ];
		} else if( params.length == 2 ) {
			offset = params[ 0 ];
			count = params[ 1 ];
		} else if( params.length == 3 ) {
			offset = params[ 0 ];
			count = params[ 1 ];
			divisions = params[ 2 ];
		} else {
			throw new IllegalArgumentException( "Invalid Crop parameter (Offset,Count[,Divisions]): " + value );
		}
		parent.setCrop( offset, count, divisions );
	}

	public void end() {
	}

	public String description() {
		return "\"Offset,Count[,Divisions]\" (Crop Count Divisions from Offset.)\n" +
			"(The Divisions parameter divides the sample into segments.)\n" +
			"(If unspecified, one division is equivalent to one sample.)";
	}
}


<|start_filename|>ibxm/Data.java<|end_filename|>

package ibxm;

/* A data array dynamically loaded from an InputStream. */
public class Data {
	private int bufLen;
	private byte[] buffer;
	private java.io.InputStream stream;

	public Data( java.io.InputStream inputStream ) throws java.io.IOException {
		bufLen = 1 << 16;
		buffer = new byte[ bufLen ];
		stream = inputStream;
		readFully( stream, buffer, 0, bufLen );
	}

	public Data( byte[] data ) {
		bufLen = data.length;
		buffer = data;
	}

	public byte sByte( int offset ) throws java.io.IOException {
		load( offset, 1 );
		return buffer[ offset ];
	}

	public int uByte( int offset ) throws java.io.IOException {
		load( offset, 1 );
		return buffer[ offset ] & 0xFF;
	}

	public int ubeShort( int offset ) throws java.io.IOException {
		load( offset, 2 );
		return ( ( buffer[ offset ] & 0xFF ) << 8 ) | ( buffer[ offset + 1 ] & 0xFF );
	}

	public int uleShort( int offset ) throws java.io.IOException {
		load( offset, 2 );
		return ( buffer[ offset ] & 0xFF ) | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 );
	}

	public int uleInt( int offset ) throws java.io.IOException {
		load( offset, 4 );
		int value = buffer[ offset ] & 0xFF;
		value = value | ( ( buffer[ offset + 1 ] & 0xFF ) << 8 );
		value = value | ( ( buffer[ offset + 2 ] & 0xFF ) << 16 );
		value = value | ( ( buffer[ offset + 3 ] & 0x7F ) << 24 );
		return value;
	}

	public String strLatin1( int offset, int length ) throws java.io.IOException {
		load( offset, length );
		char[] str = new char[ length ];
		for( int idx = 0; idx < length; idx++ ) {
			int chr = buffer[ offset + idx ] & 0xFF;
			str[ idx ] = chr < 32 ? 32 : ( char ) chr;
		}
		return new String( str );
	}

	public String strCp850( int offset, int length ) throws java.io.IOException {
		load( offset, length );
		try {
			char[] str = new String( buffer, offset, length, "Cp850" ).toCharArray();
			for( int idx = 0; idx < str.length; idx++ ) {
				str[ idx ] = str[ idx ] < 32 ? 32 : str[ idx ];
			}
			return new String( str );
		} catch( java.io.UnsupportedEncodingException e ) {
			return strLatin1( offset, length );
		}
	}

	public short[] samS8( int offset, int length ) throws java.io.IOException {
		load( offset, length );
		short[] sampleData = new short[ length ];
		for( int idx = 0; idx < length; idx++ ) {
			sampleData[ idx ] = ( short ) ( buffer[ offset + idx ] << 8 );
		}
		return sampleData;
	}

	public short[] samS8D( int offset, int length ) throws java.io.IOException {
		load( offset, length );
		short[] sampleData = new short[ length ];
		int sam = 0;
		for( int idx = 0; idx < length; idx++ ) {
			sam += buffer[ offset + idx ];
			sampleData[ idx ] = ( short ) ( sam << 8 );
		}
		return sampleData;
	}

	public short[] samU8( int offset, int length ) throws java.io.IOException {
		load( offset, length );
		short[] sampleData = new short[ length ];
		for( int idx = 0; idx < length; idx++ ) {
			sampleData[ idx ] = ( short ) ( ( ( buffer[ offset + idx ] & 0xFF ) - 128 ) << 8 );
		}
		return sampleData;
	}

	public short[] samS16( int offset, int samples ) throws java.io.IOException {
		load( offset, samples * 2 );
		short[] sampleData = new short[ samples ];
		for( int idx = 0; idx < samples; idx++ ) {
			sampleData[ idx ] = ( short ) ( ( buffer[ offset + idx * 2 ] & 0xFF ) | ( buffer[ offset + idx * 2 + 1 ] << 8 ) );
		}
		return sampleData;
	}

	public short[] samS16D( int offset, int samples ) throws java.io.IOException {
		load( offset, samples * 2 );
		short[] sampleData = new short[ samples ];
		int sam = 0;
		for( int idx = 0; idx < samples; idx++ ) {
			sam += ( buffer[ offset + idx * 2 ] & 0xFF ) | ( buffer[ offset + idx * 2 + 1 ] << 8 );
			sampleData[ idx ] = ( short ) sam;
		}
		return sampleData;
	}

	public short[] samU16( int offset, int samples ) throws java.io.IOException {
		load( offset, samples * 2 );
		short[] sampleData = new short[ samples ];
		for( int idx = 0; idx < samples; idx++ ) {
			int sam = ( buffer[ offset + idx * 2 ] & 0xFF ) | ( ( buffer[ offset + idx * 2 + 1 ] & 0xFF ) << 8 );
			sampleData[ idx ] = ( short ) ( sam  - 32768 );
		}
		return sampleData;
	}

	private void load( int offset, int length ) throws java.io.IOException {
		while( offset + length > bufLen ) {
			int newBufLen = bufLen << 1;
			byte[] newBuf = new byte[ newBufLen ];
			System.arraycopy( buffer, 0, newBuf, 0, bufLen );
			if( stream != null ) {
				readFully( stream, newBuf, bufLen, newBufLen - bufLen );
			}
			bufLen = newBufLen;
			buffer = newBuf;
		}
	}

	private static void readFully( java.io.InputStream inputStream, byte[] buffer, int offset, int length ) throws java.io.IOException {
		int read = 1, end = offset + length;
		while( read > 0 ) {
			read = inputStream.read( buffer, offset, end - offset );
			offset += read;
		}
	}
}


<|start_filename|>micromod/compiler/Name.java<|end_filename|>
package micromod.compiler;

public class Name implements Element {
	private Instrument parent;
	private Volume sibling;

	public Name( Instrument parent ) {
		this.parent = parent;
		sibling = new Volume( parent );
	}
	
	public String getToken() {
		return "Name";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setName( value );
	}
	
	public void end() {
	}

	public String description() {
		return "\"Name\" (Instrument name, maximum 22 characters.)";
	}
}


<|start_filename|>micromod/compiler/FineTune.java<|end_filename|>
package micromod.compiler;

public class FineTune implements Element {
	private Instrument parent;
	private Waveform sibling;

	public FineTune( Instrument parent ) {
		this.parent = parent;
		sibling = new Waveform( parent );
	}
	
	public String getToken() {
		return "FineTune";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setFineTune( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"0\" (Fine-tuning in eighths of a semitone, from -8 to 7.)";
	}
}


<|start_filename|>micromod/compiler/Repeat.java<|end_filename|>

package micromod.compiler;

public class Repeat implements Element {
	private Note parent;
	
	public Repeat( Note parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "Repeat";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		if( "Begin".equals( value ) ) {
			parent.setRepeat( 1 );
		} else {
			parent.setRepeat( Parser.parseInteger( value ) );
		}
	}

	public void end() {
	}

	public String description() {
		return "\"Count\" (Repeat the notes from the marker Count times.)\n" +
			"(Use a value of 'Begin' to set the marker.)";
	}
}


<|start_filename|>micromod/Scale.java<|end_filename|>

package micromod;

public class Scale {
	public static final String
		CHROMATIC = "C#D#EF#G#A#B",
		C_MAJOR   = "C-D-EF-G-A-B",
		C_MINOR   = "C-D#-F-G#-#-";
	
	private byte[] keys = new byte[ 12 * 10 ];

	public Scale() {
		this( CHROMATIC );
	}

	public Scale( String scale ) {
		if( scale.length() != 12 ) {
			throw new IllegalArgumentException( "Scale has incorrect length: " + scale );
		}
		int noteIdx = 1;
		for( int idx = 1; idx < keys.length; idx++ ) {
			int chrIdx = ( idx - 1 ) % 12;
			char chr = scale.charAt( chrIdx );
			if( chr == CHROMATIC.charAt( chrIdx ) ) {
				keys[ idx ] = ( byte ) noteIdx++;
			} else if( chr != '-' ) {
				throw new IllegalArgumentException( "Invalid character in scale: " + scale );
			}
		}
	}
	
	public int getDistance( int srcKey, int destKey ) {
		return getNoteIdx( destKey ) - getNoteIdx( srcKey );
	}
	
	/* Transpose a key the specified number of notes on the scale.*/
	public int transpose( int key, int distance ) {
		int noteIdx = getNoteIdx( key );
		while( key > 0 && keys[ key ] != noteIdx + distance ) {
			key = ( key + ( distance > 0 ? 1 : -1 ) ) % keys.length;
		}
		return key;
	}
	
	/* Return a String representation of this scale transposed
	   the specified number of semitones. */
	public String transpose( int semitones ) {
		if( semitones > 0 ) {
			semitones = semitones % 12;
		}
		char[] chars = new char[ 12 ];
		for( int idx = 0; idx < 12; idx++ ) {
			if( keys[ ( idx + 12 - semitones ) % 12 + 1 ] > 0 ) {
				chars[ idx ] = CHROMATIC.charAt( idx );
			} else {
				chars[ idx ] = '-';
			}
		}
		return new String( chars );
	}
	
	public String toString() {
		return transpose( 0 );
	}
	
	private int getNoteIdx( int key ) {
		int noteIdx = keys[ key ];
		if( noteIdx == 0 ) {
			throw new IllegalArgumentException( "Key not in scale: " + Note.keyToString( key ) );
		}
		return noteIdx;
	}
}


<|start_filename|>micromod/compiler/Sequence.java<|end_filename|>
package micromod.compiler;

public class Sequence implements Element {
	private Module parent;
	private Instrument sibling;

	public Sequence( Module parent ) {
		this.parent = parent;
		sibling = new Instrument( parent );
	}
	
	public String getToken() {
		return "Sequence";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		int[] sequence = Parser.parseIntegerArray( value );
		parent.setSequenceLength( sequence.length );
		for( int idx = 0; idx < sequence.length; idx++ ) {
			parent.setSequenceEntry( idx, sequence[ idx ] );
		}
	}
	
	public void end() {
	}

	public String description() {
		return "\"0,1,2,...\" (The order in which Patterns are played in the song.)";
	}
}


<|start_filename|>micromod/compiler/Gain.java<|end_filename|>
package micromod.compiler;

public class Gain implements Element {
	private WaveFile parent;
	private Pitch sibling;

	public Gain( WaveFile parent ) {
		this.parent = parent;
		sibling = new Pitch( parent );
	}
	
	public String getToken() {
		return "Gain";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setGain( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"64\" (Amplification factor, divided by 64.)";
	}
}


<|start_filename|>micromod/compiler/Element.java<|end_filename|>

package micromod.compiler;

public interface Element {
	public String getToken();
	public Element getParent();
	public Element getSibling();
	public Element getChild();
	public void begin( String value );
	public void end();
	public String description();
}


<|start_filename|>micromod/Player.java<|end_filename|>

package micromod;

import javax.sound.sampled.AudioFormat;
import javax.sound.sampled.AudioSystem;
import javax.sound.sampled.SourceDataLine;

public class Player implements Runnable {
	public static final int SAMPLE_RATE = 48000;
	private Module module;
	private Micromod micromod;
	private boolean playing, loop;
	private int duration;

	public Player( Module module, boolean interpolation, boolean loop ) {
		this.module = module;
		micromod = new Micromod( module, SAMPLE_RATE );
		micromod.setInterpolation( interpolation );
		duration = micromod.calculateSongDuration();
		this.loop = loop;
	}

	public int getDuration() {
		return duration;
	}

	public String getModuleInfo() {
		StringBuffer output = new StringBuffer();
		output.append( "Micromod " + Micromod.VERSION + '\n' );
		output.append( "Song name: " + module.getSongName() + '\n' );
		for( int idx = 1; idx <= 31; idx++ ) {
			String name = module.getInstrument( idx ).getName();
			if( name.trim().length() > 0 ) {
				output.append( String.format( "%1$3d ", idx ) + name + '\n' );
			}
		}
		output.append( "Song length: " + ( duration / SAMPLE_RATE ) + " seconds." );
		return output.toString();
	}

	public void stop() {
		playing = false;
	}
	
	public void run() {
		try {
			int[] mixBuf = new int[ micromod.getMixBufferLength() ];
			byte[] outBuf = new byte[ mixBuf.length * 4 ];
			AudioFormat audioFormat = new AudioFormat( SAMPLE_RATE, 16, 2, true, true );
			SourceDataLine audioLine = AudioSystem.getSourceDataLine( audioFormat );
			try {
				audioLine.open();
				audioLine.start();
				int samplePos = 0;
				playing = true;
				while( playing && ( samplePos < duration || loop ) ) {
					int count = micromod.getAudio( mixBuf );
					int outIdx = 0;
					for( int mixIdx = 0, mixEnd = count * 2; mixIdx < mixEnd; mixIdx++ ) {
						int sam = mixBuf[ mixIdx ];
						if( sam > 32767 ) sam = 32767;
						if( sam < -32768 ) sam = -32768;
						outBuf[ outIdx++ ] = ( byte ) ( sam >> 8 );
						outBuf[ outIdx++ ] = ( byte )  sam;
					}
					audioLine.write( outBuf, 0, outIdx );
					samplePos += count;
				}
				audioLine.drain();
			} finally {
				audioLine.close();
			}
		} catch( Exception e ) {
		}
	}

	public static void main( String[] args ) throws java.io.IOException {
		// Parse arguments.
		String modFile = "";
		boolean interpolation = false;
		if( args.length == 2 && "-int".equals( args[ 0 ] ) ) {
			interpolation = true;
			modFile = args[ 1 ];
		} else if( args.length == 1 ) {
			modFile = args[ 0 ];
		} else {
			System.err.println( "Micromod Java ProTracker Replay " + Micromod.VERSION );
			System.err.println( "Usage: java " + Player.class.getName() + " [-int] modfile" );
			System.exit( 1 );
		}
		// Load and play module.
		Module module = new Module( new java.io.FileInputStream( modFile ) );
		Player player = new Player( module, interpolation, false );
		System.out.println( player.getModuleInfo() );
		Thread thread = new Thread( player );
		thread.start();
		try {
			thread.join();
		} catch( InterruptedException e ) {
			System.err.println( "Interrupted!" );
		}
	}
}


<|start_filename|>micromod/compiler/Point.java<|end_filename|>
package micromod.compiler;

public class Point implements Element {
	private Waveform parent;

	public Point( Waveform parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "Point";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		int[] points = Parser.parseIntegerArray( value );
		if( points.length < 2 ) {
			throw new IllegalArgumentException( "Invalid envelope point (x,y): " + value );
		}
		int x = points[ 0 ];
		int y = points[ 1 ];
		parent.setEnvelopePoint( x, y );
		for( int idx = 2; idx < points.length; idx++ ) {
			parent.setEnvelopePoint( ++x, points[ idx ] );
		}
	}
	
	public void end() {
	}

	public String description() {
		return "\"X,Y\" (Set a point in the time or spectral envelope.)\n" +
			"(If Sawtooth or Square waveform, set sample X from 0 to 511.)\n" +
			"(If Waveform is Sine or Noise, set harmonic X from 1 to 256.)\n" +
			"(The value of Y is an eight-bit quantity from -128 to 127.)\n" +
			"(Multiple Points with increasing X are linear interpolated.)\n" +
			"(Neighbouring values can also be set with 'X,Y0,Y1,...,Yn'.)";
	}
}


<|start_filename|>micromod/compiler/Macro.java<|end_filename|>

package micromod.compiler;

public class Macro implements Element {
	private Module parent;
	private Pattern sibling;
	private Scale child = new Scale( this );
	private micromod.Pattern pattern;
	private int macroIdx, rowIdx, repeatRow;
	private String scale, root;

	public Macro( Module parent ) {
		this.parent = parent;
		sibling = new Pattern( parent );
	}
	
	public String getToken() {
		return "Macro";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return child;
	}
	
	public void begin( String value ) {
		pattern = new micromod.Pattern( 1 );
		macroIdx = Parser.parseInteger( value );
		rowIdx = repeatRow = 0;
	}

	public void end() {
		parent.setMacro( macroIdx, new micromod.Macro( scale, root, pattern ) );
	}

	public String description() {
		return "\"Index\" (Macro index, from 1 to 99.)";
	}

	public void setScale( String scale ) {
		this.scale = scale;
	}
	
	public void setRoot( String root ) {
		this.root = root;
	}

	public void nextNote( micromod.Note note, int repeatParam ) {
		pattern.setNote( rowIdx++, 0, note );
		micromod.Module module = parent.getModule();
		if( repeatParam > 0 ) {
			int repeatEnd = rowIdx;
			for( int idx = 1; idx < repeatParam; idx++ ) {
				for( int row = repeatRow; row < repeatEnd; row++ ) {
					pattern.getNote( row, 0, note );
					pattern.setNote( rowIdx++, 0, note );
				}
			}
			repeatRow = rowIdx;
		}
	}
}


<|start_filename|>micromod/compiler/Waveform.java<|end_filename|>
package micromod.compiler;

public class Waveform implements Element {
	private Instrument parent;
	private WaveFile sibling;
	private Octave child = new Octave( this );
	private byte[] envelope = new byte[ 512 ];
	private int octave, cycles, modRate, lfoRate, detune, mix, x0, y0;
	private boolean spectral, noise;
	private AudioData audioData;

	public Waveform() {
	}

	public Waveform( Instrument parent ) {
		this.parent = parent;
		sibling = new WaveFile( parent );
	}
	
	public String getToken() {
		return "Waveform";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return child;
	}
	
	public void begin( String value ) {
		spectral = noise = false;
		if( "Sawtooth".equals( value ) ) {
			setEnvelopePoint(   0, -128 );
			setEnvelopePoint( 511,  127 );
		} else if( "Square".equals( value ) ) {
			setEnvelopePoint(   0, -128 );
			setEnvelopePoint( 255, -128 );
			setEnvelopePoint( 256,  127 );
			setEnvelopePoint( 511,  127 );
		} else if( "Sine".equals( value ) ) {
			spectral = true;
			setEnvelopePoint(   0,    0 );
			setEnvelopePoint(   1, -128 );
			setEnvelopePoint(   2,    0 );
			setEnvelopePoint( 511,    0 );
		} else if( "Noise".equals( value ) ) {
			spectral = noise = true;
			setEnvelopePoint( 0, 0 );
			setEnvelopePoint( 1, 1 );
			setEnvelopePoint( 256, 1 );
			setEnvelopePoint( 257, 0 );
			setEnvelopePoint( 511, 0 );
		} else {
			throw new IllegalArgumentException( "Invalid waveform type: " + value );
		}
		setOctave( 0 );
		setModulation( 1, 0, 0, 0, 128 );
	}

	public void end() {
		if( noise ) {
			setAudioData( new AudioData( noise( envelope ), 512 * 262 ) );
		} else {
			byte[] modulator = cosine();
			byte[] carrier = spectral ? harmonics( envelope ) : envelope;
			byte[] waveform = new byte[ cycles * 512 ];
			fm( carrier, modulator, modulator, cycles, modRate, lfoRate, detune, mix, waveform );
			if( octave > -4 ) {
				byte[] outBuf = new byte[ waveform.length + 1024 ];
				for( int idx = 0; idx < outBuf.length; idx++ ) {
					outBuf[ idx ] = waveform[ idx % waveform.length ];
				}
				AudioData audioData = new AudioData( outBuf, 512 * 262 );
				audioData = audioData.resample( audioData.getSamplingRate() >> ( octave + 4 ) );
				audioData = audioData.crop( 512 >> ( octave + 4 ), audioData.getLength() - ( 1024 >> ( octave + 4 ) ) );
				setAudioData( audioData );
			} else {
				setAudioData( new AudioData( waveform, 512 * 262 ) );
			}
		}
	}

	public String description() {
		return "\"Type\" ('Sawtooth', 'Square', 'Sine', or 'Noise'.)";
	}

	public AudioData getAudioData() {
		return audioData;
	}

	public void setAudioData( AudioData audioData ) {
		this.audioData = audioData;
		if( parent != null ) {
			parent.setAudioData( audioData );
			parent.setLoopStart( 0 );
			parent.setLoopLength( audioData.getLength() );
		}
	}

	public void setOctave( int octave ) {
		if( octave < -4 || octave > 4 ) {
			throw new IllegalArgumentException( "Invalid octave (-4 to 4): " + octave );
		}
		this.octave = octave;
	}

	public void setModulation( int cycles, int modRate, int lfoRate, int detune, int mix ) {
		if( cycles < 1 || cycles > 1024 ) {
			throw new IllegalArgumentException( "Invalid number of cycles (1 to 1024): " + cycles );
		}
		this.cycles = cycles;
		if( modRate < 0 || modRate > 1024 ) {
			throw new IllegalArgumentException( "Invalid modulation rate (0 to 1024): " + modRate );
		}
		this.modRate = modRate;
		if( lfoRate < 0 || lfoRate > 1024 ) {
			throw new IllegalArgumentException( "Invalid LFO rate (0 to 1024): " + lfoRate );
		}
		this.lfoRate = lfoRate;
		if( detune < -192 || detune > 192 ) {
			throw new IllegalArgumentException( "Invalid detune pitch (-192 to 192): " + detune );
		}
		this.detune = detune;
		if( mix < 0 || mix > 256 ) {
			throw new IllegalArgumentException( "Invalid mix parameter (0 to 256): " + mix );
		}
		this.mix = mix;
	}

	/* Set the envelope from x0 to x1 (0 to 511) by interpolating y0 to y1 (-128 to 127).
	   The values of x0 and y0 are taken from the previously set envelope point, unless
	   x1 is less than or equal to x0, which sets a single point without interpolation.*/
	public void setEnvelopePoint( int x1, int y1 ) {
		if( x1 < 0 || x1 > 511 ) {
			throw new IllegalArgumentException( "Invalid envelope index (0 to 511): " + x1 );
		}
		if( y1 < -128 || y1 > 127 ) {
			throw new IllegalArgumentException( "Invalid envelope amplitude (-128 to 127): " + y1 );
		}
		if( x1 <= x0 ) {
			envelope[ x1 ] = ( byte ) y1;
		} else {
			for( int x = 0, dx = x1 - x0, dy = y1 - y0; x <= dx; x++ ) {
				envelope[ x0 + x ] = ( byte ) ( dy * x / dx + y0 );
			}
		}
		x0 = x1;
		y0 = y1;
	}

	/* Generate a 512-byte periodic waveform from the specified 256-harmonic spectrum. */
	public static byte[] harmonics( byte[] spectrum ) {
		int[] window = new int[ 512 ];
		for( int idx = 0; idx < 512; idx++ ) {
			window[ idx ] = 256;
		}
		byte[] output = new byte[ 512 ];
		additive( sine(), spectrum, new int[ 512 ], window, output, 0, 0x1FF );
		return output;
	}

	/* Generate a 65536-byte noise waveform from the specified 256-harmonic spectrum. */
	public static byte[] noise( byte[] spectrum ) {
		byte[] sine = sine();
		int[] window = new int[ 512 ];
		for( int idx = 0; idx < 256; idx++ ) {
			window[ idx ] = idx;
			window[ idx + 256 ] = 256 - idx;
		}
		int random = 0;
		int[] phase = new int[ 512 ];
		byte[] output = new byte[ 65536 ];
		for( int offset = 0; offset < 65536; offset += 256 ) {
			for( int idx = 0; idx < 512; idx++ ) {
				random = random * 65 + 17;
				phase[ idx ] = ( random >> 24 ) & 0x1FF;
			}
			additive( sine, spectrum, phase, window, output, offset, 0xFFFF );
		}
		return output;
	}

	/* Simple additive synthesis. */
	public static void additive( byte[] sine, byte[] spectrum, int[] phase, int[] window, byte[] output, int offset, int mask ) {
		for( int idx = 0; idx < 512; idx++ ) {
			int amp = 0;
			for( int partial = 1; partial <= 256; partial++ ) {
				amp = amp + sine[ ( phase[ partial ] + idx * partial ) & 0x1FF ] * spectrum[ partial ];
			}
			int outIdx = ( offset + idx ) & mask;
			amp = ( output[ outIdx ] * 32768 + amp * window[ idx ] ) / 32768;
			if( amp < -128 ) amp = -128;
			if( amp >  127 ) amp =  127;
			output[ outIdx ] = ( byte ) amp;
		}
	}

	/* Simple 3-operator FM synthesis. */
	public static void fm( byte[] carrier, byte[] modulator, byte[] lfo, int cycles, int modRate, int lfoRate, int detune, int mix, byte[] output ) {
		int cycles2 = Math.round( ( float ) ( cycles * Math.pow( 2, detune / 96d ) ) );
		for( int idx = 0, end = cycles * 512; idx < end; idx++ ) {
			int out1 = carrier[ idx & 0x1FF ];
			int out2 = carrier[ ( idx * cycles2 / cycles + ( 128 + modulator[ ( idx * modRate / cycles ) & 0x1FF ] ) * ( 128 + lfo[ ( idx * lfoRate / cycles ) & 0x1FF ] ) / 128 ) & 0x1FF ];
			output[ idx ] = ( byte ) ( ( out1 * mix + out2 * ( 256 - mix ) ) / 256 );
		}
	}

	/* Generate a 512-byte sine table. */
	public static byte[] sine() {
		byte[] sine = new byte[ 512 ];
		for( int idx = 0; idx < 512; idx++ ) {
			sine[ idx ] = ( byte ) Math.round( Math.sin( Math.PI * idx / 256 ) * 127 );
		}
		return sine;
	}

	public static byte[] cosine() {
		byte[] cosine = new byte[ 512 ];
		for( int idx = 0; idx < 512; idx++ ) {
			cosine[ idx ] = ( byte ) Math.round( Math.cos( Math.PI * idx / 256 ) * 127 );
		}
		return cosine;
	}

	public static byte[] sawtooth() {
		byte[] saw = new byte[ 512 ];
		for( int idx = 0; idx < 512; idx++ ) {
			saw[ idx ] = ( byte ) ( idx / 2 - 128 );
		}
		return saw;
	}

	public static byte[] triangle() {
		byte[] tri = new byte[ 512 ];
		for( int idx = 0; idx < 256; idx++ ) {
			tri[ idx ] = ( byte ) ( idx - 128 );
			tri[ idx + 256 ] = ( byte ) ( 127 - idx );
		}
		return tri;
	}

	public static void main( String[] args ) throws java.io.IOException {
		int idx = 0;
		String patch = "Waveform Sawtooth Octave -2 Chorus 1024,0", waveFile = null;
		while( idx < args.length ) {
			String arg = args[ idx++ ];
			if( "-patch".equals( arg ) ){
				patch = args[ idx++ ];
			} else if( "-wav".equals( arg ) ) {
				waveFile = args[ idx++ ];
			}
		}
		Waveform waveform = new Waveform();
		if( waveFile != null ) {
			Parser.parse( new java.io.StringReader( patch ), waveform );
			java.io.OutputStream outputStream = new java.io.FileOutputStream( waveFile );
			try {
				AudioData audioData = waveform.getAudioData();
				audioData.setSamplingRate( 512 * 262 / 4 );
				audioData.writeWav( outputStream, false );
			} finally {
				outputStream.close();
			}
		} else {
			System.err.println( "Usage: " + Waveform.class.getName() +
				"\n      -patch \"" + patch + "\" -wav output.wav" );
			System.err.println( "Patch syntax: " + Parser.syntax( waveform ) );
		}
	}
}


<|start_filename|>micromod/compiler/Note.java<|end_filename|>

package micromod.compiler;

public class Note implements Element {
	private Macro parent;
	private Repeat child = new Repeat( this );
	private micromod.Note note = new micromod.Note();
	private int repeatCount;

	public Note( Macro parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "Note";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return child;
	}
	
	public void begin( String value ) {
		repeatCount = 0;
		note.fromString( value );
	}

	public void end() {
		parent.nextNote( note, repeatCount );
	}

	public String description() {
		return "\"C-2-1---\" (A single 8-character note, as used in a Pattern.)";
	}

	public void setRepeat( int count ) {
		if( repeatCount < 1 ) {
			repeatCount = count;
		} else {
			repeatCount *= count;
		}
	}
}


<|start_filename|>micromod/compiler/Channels.java<|end_filename|>
package micromod.compiler;

public class Channels implements Element {
	private Module parent;
	private Sequence sibling;

	public Channels( Module parent ) {
		this.parent = parent;
		sibling = new Sequence( parent );
	}
	
	public String getToken() {
		return "Channels";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setNumChannels( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"4\" (Number of channels. Four channel modules have fixed panning.)";
	}
}


<|start_filename|>micromod/Macro.java<|end_filename|>

package micromod;

public class Macro {
	private Scale scale;
	private int rootKey;
	private Pattern notes;
	
	public Macro( String scale, String rootKey, Pattern notes ) {
		this.scale = new Scale( scale != null ? scale : Scale.CHROMATIC );
		this.rootKey = Note.parseKey( rootKey != null ? rootKey : "C-2" );
		this.notes = new Pattern( 1, notes );
	}

	/* Expand macro into the specified position in the pattern list.
	   The speed parameter must match the effect 0xF speed for proper transpose (default 6).
	   Expansion will continue into the next pattern until an instrument is encountered. */
	public int expand( Module module, Pattern[] patterns, int patternIdx, int rowIdx, int channelIdx, int speed ) {
		int macroRowIdx = 0, srcKey = rootKey, dstKey = rootKey, distance = 0, amplitude = 64;
		int volume = 0, fineTune = 0, period = 0, portaPeriod = 0, portaSpeed = 0;
		int sampleOffset = 0, sampleLength = 0, delta;
		if( speed < 1 ) {
			speed = 1;
		}
		Note note = new Note();
		while( macroRowIdx < Pattern.NUM_ROWS ) {
			patternIdx += rowIdx / Pattern.NUM_ROWS;
			rowIdx = rowIdx % Pattern.NUM_ROWS;
			if( patternIdx >= patterns.length ) {
				break;
			}
			Pattern pattern = patterns[ patternIdx ];
			pattern.getNote( rowIdx, channelIdx, note );
			if( note.instrument > 0 ) {
				break;
			}
			if( note.key > 0 ) {
				distance = scale.getDistance( rootKey, note.key );
			}
			int effect = note.effect;
			int param = note.parameter;
			notes.getNote( macroRowIdx++, 0, note );
			if( effect > 0 ) {
				if( effect == 0xC ) {
					amplitude = ( param < 64 ) ? param : 64;
				}
				if( effect != 0xC || param == 0 ) {
					/* Ensure C00 is passed through to empty notes. */
					note.effect = effect;
					note.parameter = param;
				}
			}
			if( note.instrument > 0 ) {
				Instrument instrument = module.getInstrument( note.instrument );
				volume = instrument.getVolume() * 4;
				fineTune = instrument.getFineTune();
				sampleLength = instrument.getLoopStart() + instrument.getLoopLength();
				if( amplitude < 64 && note.effect != 0xC ) {
					/* Setting an instrument sets the volume. */
					note.effect = 0xC;
					note.parameter = instrument.getVolume();
				}
			}
			if( note.effect == 0xE && ( note.parameter & 0xF0 ) == 0x50 ) {
				fineTune = ( note.parameter & 0x7 ) - ( note.parameter & 0x8 );
			}
			if( note.key > 0 ) {
				srcKey = note.key;
				dstKey = scale.transpose( srcKey, distance );
				note.key = dstKey;
				portaPeriod = Note.keyToPeriod( note.key, fineTune );
				if( note.effect != 0x3 && note.effect != 0x5 ) {
					period = portaPeriod;
				}
				if( note.effect != 0x9 ) {
					sampleOffset = 0;
				}
			}
			if( volume < 0 ) {
				volume = 0;
			}
			if( volume > 256 ) {
				volume = 256;
			}
			if( period < 0 ) {
				period = 0;
			}
			if( note.effect == 0 && note.parameter != 0 ) {
				/* Arpeggio.*/
				delta = scale.getDistance( srcKey, srcKey + ( ( note.parameter >> 4 ) & 0xF ) );
				delta = scale.transpose( dstKey, delta ) - dstKey;
				if( delta < 0 ) {
					delta = 0;
				}
				if( delta > 15 ) {
					delta = ( delta - 3 ) % 12 + 3;
				}
				note.parameter = ( delta << 4 ) + ( note.parameter & 0xF );
				delta = scale.getDistance( srcKey, srcKey + ( note.parameter & 0xF ) );
				delta = scale.transpose( dstKey, delta ) - dstKey;
				if( delta < 0 ) {
					delta = 0;
				}
				if( delta > 15 ) {
					delta = ( delta - 3 ) % 12 + 3;
				}
				note.parameter = ( note.parameter & 0xF0 ) + delta;
			} else if( note.effect == 0x1 ) {
				/* Portamento up. */
				if( note.parameter > 0xF0 && note.parameter < 0xFE ) {
					delta = period - Note.keyToPeriod( Note.periodToKey( period ) + ( note.parameter & 0xF ), 0 );
				} else {
					delta = Note.transpose( note.parameter * ( speed - 1 ), dstKey - srcKey );
				}
				note.parameter = ( speed > 1 ) ? divide( delta, speed - 1, 0xFF ) : 0;
				period = period - note.parameter * ( speed - 1 );
				if( note.parameter == 0 ) {
					note.effect = 0xE;
					note.parameter = 0x10 + ( delta & 0xF );
					period = period - ( delta & 0xF );
				}
			} else if( note.effect == 0x2 ) {
				/* Portamento down. */
				if( note.parameter > 0xF0 && note.parameter < 0xFE ) {
					delta = Note.keyToPeriod( Note.periodToKey( period ) - ( note.parameter & 0xF ), 0 ) - period;
				} else {
					delta = Note.transpose( note.parameter * ( speed - 1 ), dstKey - srcKey );
				}
				note.parameter = ( speed > 1 ) ? divide( delta, speed - 1, 0xFF ) : 0;
				period = period + note.parameter * ( speed - 1 );
				if( note.parameter == 0 ) {
					note.effect = 0xE;
					note.parameter = 0x20 + ( delta & 0xF );
					period = period + ( delta & 0xF );
				}
			} else if( note.effect == 0x3 || note.effect == 0x5 ) {
				/* Tone portamento. */
				if( note.effect == 0x3 && note.parameter > 0 ) {
					if( note.parameter > 0xF0 && note.parameter < 0xFE ) {
						if( portaPeriod < period ) {
							delta = period - Note.keyToPeriod( Note.periodToKey( period ) + ( note.parameter & 0xF ), 0 );
						} else {
							delta = Note.keyToPeriod( Note.periodToKey( period ) - ( note.parameter & 0xF ), 0 ) - period;
						}
						note.parameter = divide( delta, ( speed > 1 ) ? ( speed - 1 ) : 1, 0xFF );
					} else {
						note.parameter = transpose( note.parameter & 0xFF, dstKey - srcKey, 0xFF );
					}
					if( note.parameter < 1 ) {
						note.parameter = 1;
					}
					portaSpeed = note.parameter;
				}
				if( period < portaPeriod ) {
					period = period + portaSpeed * ( speed - 1 );
					if( period > portaPeriod ) {
						period = portaPeriod;
					}
				} else {
					period = period - portaSpeed * ( speed - 1 );
					if( period < portaPeriod ) {
						period = portaPeriod;
					}
				}
			} else if( note.effect == 0x4 ) {
				/* Vibrato. */
				note.parameter = ( note.parameter & 0xF0 ) + transpose( note.parameter & 0xF, dstKey - srcKey, 0xF );
			} else if( note.effect == 0x9 ) {
				/* Set sample offset. */
				if( note.parameter >= 0xF0 ) {
					sampleOffset = sampleOffset + sampleLength * ( note.parameter & 0xF ) / 64;
					note.parameter = divide( sampleOffset, 256, 0xFF );
				} else {
					sampleOffset = ( note.parameter & 0xFF ) << 8;
				}
			} else if( note.effect == 0xE && ( note.parameter & 0xF0 ) == 0x10 ) {
				/* Fine portamento up. */
				note.parameter = 0x10 + transpose( note.parameter & 0xF, dstKey - srcKey, 0xF );
				period = period - ( note.parameter & 0xF );
			} else if( note.effect == 0xE && ( note.parameter & 0xF0 ) == 0x20 ) {
				/* Fine portamento down. */
				note.parameter = 0x20 + transpose( note.parameter & 0xF, dstKey - srcKey, 0xF );
				period = period + ( note.parameter & 0xF );
			}
			if( note.effect == 0x5 || note.effect == 0x6 || note.effect == 0xA ) {
				/* Volume slide. */
				if( ( note.parameter & 0xF ) == 0xF ) {
					delta = ( ( note.parameter & 0xF0 ) >> 4 ) + 1;
					delta = sqrt( volume * 256 ) + divide( 256, delta * 4, 256 );
					delta = divide( delta * delta, 256, 256 ) - volume;
					if( delta < 1 ) {
						delta = 1;
					}
				} else if( ( note.parameter & 0xF0 ) == 0xF0 ) {
					delta = ( note.parameter & 0xF ) + 1;
					delta = sqrt( volume * 256 ) - divide( 256, delta * 4, 256 );
					if( delta < 0 ) {
						delta = 0;
					}
					delta = divide( delta * delta, 256, 256 ) - volume;
					if( delta > -1 ) {
						delta = -1;
					}
				} else {
					delta = divide( ( ( note.parameter & 0xF0 ) >> 4 ) * 4 * amplitude, 64, 256 );
					delta = delta - divide( ( note.parameter & 0xF ) * 4 * amplitude, 64, 256 );
					delta = delta * ( speed - 1 );
				}
				if( volume + delta > amplitude * 4 ) {
					delta = amplitude * 4 - volume;
				}
				if( speed > 1 && divide( delta, ( speed - 1 ) * 4, 0xF ) > 1 ) {
					delta = divide( delta, ( speed - 1 ) * 4, 0xF );
					note.parameter = delta << 4;
					volume = volume + delta * ( speed - 1 ) * 4;
				} else if( speed > 1 && divide( -delta, ( speed - 1 ) * 4, 0xF ) > 1 ) {
					delta = divide( -delta, ( speed - 1 ) * 4, 0xF );
					note.parameter = delta;
					volume = volume - delta * ( speed - 1 ) * 4;
				} else if( note.effect == 0xA ) {
					volume = volume + delta;
					note.effect = 0xC;
					note.parameter = divide( volume, 4, 64 );
				} else if( delta > 0 ) {
					note.parameter = 0x10;
					volume = volume + ( speed - 1 ) * 4;
				} else if( delta < 0 ) {
					note.parameter = 0x1;
					volume = volume - ( speed - 1 ) * 4;
				}
			} else if( note.effect == 0x7 ) {
				/* Tremolo. */
				note.parameter = ( note.parameter & 0xF0 ) + divide( ( note.parameter & 0xF ) * amplitude, 64, 0xF );
			} else if( note.effect == 0xC ) {
				/* Set volume. */
				if( ( note.parameter & 0xF0 ) == 0xF0 ) {
					note.parameter = ( note.parameter & 0xF ) + 1;
					note.parameter = ( note.parameter * note.parameter ) >> 2;
				}
				note.parameter = divide( note.parameter * amplitude, 64, amplitude );
				volume = note.parameter * 4;
			} else if( note.effect == 0xE && ( note.parameter & 0xF0 ) == 0xA0 ) {
				/* Fine volume slide up. */
				delta = divide( ( note.parameter & 0xF ) * 4 * amplitude, 64, 256 );
				volume = volume + delta;
				note.effect = 0xC;
				note.parameter = divide( volume, 4, amplitude );
			} else if( note.effect == 0xE && ( note.parameter & 0xF0 ) == 0xB0 ) {
				/* Fine volume slide down. */
				delta = divide( ( note.parameter & 0xF ) * 4 * amplitude, 64, 256 );
				volume = volume - delta;
				note.effect = 0xC;
				note.parameter = divide( volume, 4, amplitude );
			}
			pattern.setNote( rowIdx++, channelIdx, note );
		}
		return rowIdx;
	}

	private static int transpose( int period, int semitones, int maximum ) {
		period = Note.transpose( period, semitones );
		return period < maximum ? period : maximum;
	}

	private static int divide( int dividend, int divisor, int maximum ) {
		/* Divide positive integers with rounding and clipping. */
		int quotient = 0;
		if( dividend > 0 ) {
			quotient = ( dividend << 1 ) / divisor;
			quotient = ( quotient >> 1 ) + ( quotient & 1 );
			if( quotient > maximum ) {
				quotient = maximum;
			}
		}
		return quotient;
	}

	private static int sqrt( int y ) {
		int x = 256;
		for( int n = 0; n < 8; n++ ) {
			x = ( x + y / x );
			x = ( x >> 1 ) + ( x & 1 );
		}
		return x;
	}
}


<|start_filename|>micromod/compiler/Pitch.java<|end_filename|>
package micromod.compiler;

public class Pitch implements Element {
	private WaveFile parent;

	public Pitch( WaveFile parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "Pitch";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setPitch( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"0\" (Tune the sample in eighths of a semitone.)";
	}
}


<|start_filename|>micromod/compiler/LoopLength.java<|end_filename|>
package micromod.compiler;

public class LoopLength implements Element {
	private Instrument parent;
	private PingPong sibling;

	public LoopLength( Instrument parent ) {
		this.parent = parent;
		sibling = new PingPong( parent );
	}
	
	public String getToken() {
		return "LoopLength";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return sibling;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		parent.setLoopLength( Parser.parseInteger( value ) );
	}
	
	public void end() {
	}

	public String description() {
		return "\"Length\" (Length of sustain phase in samples.)";
	}
}


<|start_filename|>micromod/compiler/PingPong.java<|end_filename|>
package micromod.compiler;

public class PingPong implements Element {
	private Instrument parent;

	public PingPong( Instrument parent ) {
		this.parent = parent;
	}
	
	public String getToken() {
		return "PingPong";
	}
	
	public Element getParent() {
		return parent;
	}
	
	public Element getSibling() {
		return null;
	}
	
	public Element getChild() {
		return null;
	}
	
	public void begin( String value ) {
		if( "On".equals( value ) ) {
			parent.setPingPong( true );
		} else if( "Off".equals( value ) ) {
			parent.setPingPong( false );
		} else {
			throw new IllegalArgumentException( "Invalid PingPong parameter (On or Off): " + value );
		}
	}
	
	public void end() {
	}

	public String description() {
		return "\"On\" (Append a reversed sustain phase to the sample.)";
	}
}
