/// <summary> /// Attempts to parse the <code>byte</code> array as an it file, populating the instance of the class this function was called from with data. /// </summary> /// <param name="data"><code>byte</code> array that contains the data, which should be a complete .it file. </param> /// <throws>FormatException</throws> public void parse( byte[] data ) { #region Parse the header magicword = read4( data, 0x00 ); if ( magicword != 0x4D504D49 ) throw new FormatException( "Magic word mismatch. File must begin with the letters \"IMPM\". Are you sure this is an it file?" ); songname = System.Text.Encoding.ASCII.GetString( data, 0x04, 26 ); pathighlight = read2( data, 0x1E ); numorders = read2( data, 0x20 ); insnum = read2( data, 0x22 ); if ( insnum != 0 ) throw new FormatException( "Instruments are detected, they are not supported. This program was made for it files that were made using OpenSPC." ); numsamples = read2( data, 0x24 ); numpatterns = read2( data, 0x26 ); createdwithtracker = read2( data, 0x28 ); compatablewithtracker = read2( data, 0x2A ); flags = read2( data, 0x2C ); special = read2( data, 0x2E ); globalvol = data[ 0x30 ]; mixvol = data[ 0x31 ]; initialspeed = data[ 0x32 ]; initialtempo = data[ 0x33 ]; panseperation = data[ 0x34 ]; pitchwheeldepth = data[ 0x35 ]; messagelength = read2( data, 0x36 ); messageoffset = read4( data, 0x38 ); if ( messagelength > 8000 ) throw new FormatException( "Message length is greater than 8000 bytes." ); message = new byte[ messagelength ]; for ( int i = 0; i < messagelength; i++ ) { message[ i ] = data[ messageoffset + i ]; } reserved = read4( data, 0x3C ); channelpan = new byte[ 64 ]; channels = 0; for ( int i = 0; i < 64; i++ ) { channelpan[ i ] = data[ 0x40 + i ]; if ( channelpan[ i ] < 128 ) channels++; } channelvol = new byte[ 64 ]; for ( int i = 0; i < 64; i++ ) { channelvol[ i ] = data[ 0x80 + i ]; } orders = new byte[ numorders ]; for ( int i = 0; i < numorders; i++ ) { orders[ i ] = data[ 0xC0 + i ]; } // ignoring instruments, since they're not supported there shouldn't be any to read sampleoffsets = new uint[ numsamples ]; for ( int i = 0; i < numsamples; i++ ) { sampleoffsets[ i ] = read4( data, 0xC0 + numorders + i * 4 ); } patternoffsets = new uint[ numpatterns ]; for ( int i = 0; i < numpatterns; i++ ) { patternoffsets[ i ] = read4( data, 0xC0 + numorders + numsamples * 4 + i * 4 ); } #endregion #region Parse the samples samples = new itsample[ numsamples ]; try { for ( int i = 0; i < numsamples; i++ ) { samples[ i ].magicword = read4( data, sampleoffsets[ i ] + 0x00 ); if ( samples[ i ].magicword != 0x53504D49 ) throw new FormatException( String.Format( "Magic word mismatch on sample #{0}, offset 0x{1:X}. Header must begin with the letters \"IMPS\". File may be corrupt.", i, sampleoffsets[ i ] ) ); samples[ i ].dosfilename = System.Text.Encoding.ASCII.GetString( data, (int) sampleoffsets[ i ] + 0x04, 12 ); samples[ i ].globalvol = data[ sampleoffsets[ i ] + 0x11 ]; samples[ i ].flags = data[ sampleoffsets[ i ] + 0x12 ]; samples[ i ].defaultvol = data[ sampleoffsets[ i ] + 0x13 ]; samples[ i ].samplename = System.Text.Encoding.ASCII.GetString( data, (int) sampleoffsets[ i ] + 0x14, 26 ); samples[ i ].convert = data[ sampleoffsets[ i ] + 0x2E ]; samples[ i ].defaultpan = data[ sampleoffsets[ i ] + 0x2F ]; samples[ i ].length = read4( data, sampleoffsets[ i ] + 0x30 ); if ( samples[ i ].length > ( 64 * 1024 * 1024 ) ) throw new FormatException( String.Format( "Sample size for sample #{0} is greater than 64MB (according to the header, it's {1}MB), aborting to avoid excessive memory usage.", i, samples[ i ].length / 1024 / 1024 ) ); samples[ i ].loopbegin = read4( data, sampleoffsets[ i ] + 0x34 ); samples[ i ].loopend = read4( data, sampleoffsets[ i ] + 0x38 ); samples[ i ].c5speed = read4( data, sampleoffsets[ i ] + 0x3C ); samples[ i ].susloopbegin = read4( data, sampleoffsets[ i ] + 0x40 ); samples[ i ].susloopend = read4( data, sampleoffsets[ i ] + 0x44 ); samples[ i ].samplepointer = read4( data, sampleoffsets[ i ] + 0x48 ); samples[ i ].vibratospeed = data[ sampleoffsets[ i ] + 0x4C ]; samples[ i ].vibratodepth = data[ sampleoffsets[ i ] + 0x4D ]; samples[ i ].vibratorate = data[ sampleoffsets[ i ] + 0x4E ]; samples[ i ].vwaveformtype = data[ sampleoffsets[ i ] + 0x4F ]; samples[ i ].sample = new byte[ samples[ i ].length * 2 ]; for ( int j = 0; j < samples[ i ].length * 2; j++ ) { samples[ i ].sample[ j ] = data[ samples[ i ].samplepointer + j ]; } } } catch ( Exception e ) { throw e; } #endregion #region Parse the patterns patterns = new itpattern[ numpatterns ]; try { for ( int i = 0; i < numpatterns; i++ ) { patterns[ i ].length = read2( data, patternoffsets[ i ] + 0x00 ); patterns[ i ].rows = read2( data, patternoffsets[ i ] + 0x02 ); patterns[ i ].packeddata = new byte[ patterns[ i ].length ]; for ( int j = 0; j < patterns[ i ].length; j++ ) { patterns[ i ].packeddata[ j ] = data[ patternoffsets[ i ] + 0x08 + j ]; } patterns[ i ].row = new rowgroup[ patterns[ i ].rows ]; for ( int row = 0; row < patterns[ i ].rows; row++ ) { patterns[ i ].row[ row ].channel = new channelgroup[ channels ]; for ( int channel = 0; channel < channels; channel++ ) { patterns[ i ].row[ row ].channel[ channel ].note = 0; patterns[ i ].row[ row ].channel[ channel ].instrument = 0; patterns[ i ].row[ row ].channel[ channel ].volume = 0; patterns[ i ].row[ row ].channel[ channel ].command = 0; patterns[ i ].row[ row ].channel[ channel ].parameter = 0; } } // now, unpack the data channelgroup[] prev = new channelgroup[ 64 ]; byte[] channelmask = new byte[ 64 ]; byte[] maskvar = new byte[ 64 ]; ushort rowsize = (ushort) ( channels * 5 ); ushort offset = 0; byte currentchannel = 0; int currentrow = 0; try { while ( currentrow < patterns[ i ].rows ) { if ( offset >= patterns[ i ].packeddata.Length ) { // sanity check throw new FormatException( String.Format( "Error parsing pattern: offset > length, pattern #{0}, offset = {1:X} length = {2:X}. The file may be corrupt.", i, offset, patterns[ i ].length ) ); //break; } byte channelvar = patterns[ i ].packeddata[ offset++ ]; if ( channelvar == 0 ) { currentrow++; continue; } if ( currentrow > 255 ) { throw new FormatException( String.Format( "Error parsing pattern: currentrow > 255, pattern #{0}. The file may be corrupt.", i ) ); //break; } currentchannel = (byte) ( ( channelvar - 1 ) & 63 ); if ( ( channelvar & 128 ) == 128 ) // will not be updated if the highest bit is not set, will instead use whatever's already there at that position maskvar[ currentchannel ] = patterns[ i ].packeddata[ offset++ ]; if ( ( maskvar[ currentchannel ] & 1 ) == 1 ) { // note command byte note = patterns[ i ].packeddata[ offset++ ]; if ( note < 0xFD ) note++; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 0 ] = note; patterns[ i ].row[ currentrow ].channel[ currentchannel ].note = note; prev[ currentchannel ].note = note; } if ( ( maskvar[ currentchannel ] & 2 ) == 2 ) { // instrument command byte instrument = patterns[ i ].packeddata[ offset++ ]; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 1 ] = instrument; patterns[ i ].row[ currentrow ].channel[ currentchannel ].instrument = instrument; prev[ currentchannel ].instrument = instrument; } if ( ( maskvar[ currentchannel ] & 4 ) == 4 ) { // volume command byte volume = (byte) ( patterns[ i ].packeddata[ offset++ ] + 1 ); //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 2 ] = volume; patterns[ i ].row[ currentrow ].channel[ currentchannel ].volume = volume; prev[ currentchannel ].volume = volume; } if ( ( maskvar[ currentchannel ] & 8 ) == 8 ) { // effect command byte command = patterns[ i ].packeddata[ offset++ ]; byte parameter = patterns[ i ].packeddata[ offset++ ]; if ( command != 0 ) { //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 3 ] = command; patterns[ i ].row[ currentrow ].channel[ currentchannel ].command = command; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 4 ] = parameter; patterns[ i ].row[ currentrow ].channel[ currentchannel ].parameter = parameter; prev[ currentchannel ].command = command; prev[ currentchannel ].parameter = parameter; } } if ( ( maskvar[ currentchannel ] & 16 ) == 16 ) { // previous note command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 0 ] = prev[ currentchannel ].note; patterns[ i ].row[ currentrow ].channel[ currentchannel ].note = prev[ currentchannel ].note; } if ( ( maskvar[ currentchannel ] & 32 ) == 32 ) { // previous instrument command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 1 ] = prev[ currentchannel ].instrument; patterns[ i ].row[ currentrow ].channel[ currentchannel ].instrument = prev[ currentchannel ].instrument; } if ( ( maskvar[ currentchannel ] & 64 ) == 64 ) { // previous volume command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 2 ] = prev[ currentchannel ].volume; patterns[ i ].row[ currentrow ].channel[ currentchannel ].volume = prev[ currentchannel ].volume; } if ( ( maskvar[ currentchannel ] & 128 ) == 128 ) { // previous effect command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 3 ] = prev[ currentchannel ].command; patterns[ i ].row[ currentrow ].channel[ currentchannel ].command = prev[ currentchannel ].command; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 4 ] = prev[ currentchannel ].parameter; patterns[ i ].row[ currentrow ].channel[ currentchannel ].parameter = prev[ currentchannel ].parameter; } } } catch ( Exception e ) { throw e; } } } catch ( Exception e ) { throw e; } #endregion }
/// <summary> /// Packs the data contained within the instance of the class this was called from into a .it file, ready to be written to the disk. /// </summary> /// <returns> A <code>byte</code> array that contains a complete it file, ready to be written to the disk. </returns> public byte[] pack() { #region Pack the patterns ushort rowsize = (ushort)(channels * 5); for (int i = 0; i < numpatterns; i++) { channelgroup[] previousvalues = new channelgroup[64]; byte[] channelmask = new byte[64]; byte[] prevmaskvar = new byte[64]; ushort length = 0; patterns[i].packeddata = new byte[256 * 64 * 7]; for (int row = 0; row < patterns[i].rows; row++) { for (int channel = 0; channel < channels; channel++) { byte maskvar = 0; if (patterns[i].row[row].channel[channel].note != 0) { maskvar |= 1; } if (patterns[i].row[row].channel[channel].instrument != 0) { maskvar |= 2; } if (patterns[i].row[row].channel[channel].volume != 0) { maskvar |= 4; } if (patterns[i].row[row].channel[channel].command != 0) { maskvar |= 8; } if (maskvar != 0) { if ((maskvar & 1) == 1) { if (patterns[i].row[row].channel[channel].note == previousvalues[channel].note) { unchecked { maskvar &= (byte)~0x01; maskvar |= 0x10; } } else { previousvalues[channel].note = patterns[i].row[row].channel[channel].note; } } if ((maskvar & 2) == 2) { if (patterns[i].row[row].channel[channel].instrument == previousvalues[channel].instrument) { unchecked { maskvar &= (byte)~0x02; maskvar |= 0x20; } } else { previousvalues[channel].instrument = patterns[i].row[row].channel[channel].instrument; } } if ((maskvar & 4) == 4) { if (patterns[i].row[row].channel[channel].volume == previousvalues[channel].volume) { unchecked { maskvar &= (byte)~0x04; maskvar |= 0x40; } } else { previousvalues[channel].volume = patterns[i].row[row].channel[channel].volume; } } if ((maskvar & 8) == 8) { if ((patterns[i].row[row].channel[channel].command == previousvalues[channel].command) && (patterns[i].row[row].channel[channel].parameter == previousvalues[channel].parameter)) { unchecked { maskvar &= (byte)~0x08; maskvar |= 0x80; } } else { previousvalues[channel].command = patterns[i].row[row].channel[channel].command; previousvalues[channel].parameter = patterns[i].row[row].channel[channel].parameter; } } if (maskvar != channelmask[channel]) { channelmask[channel] = maskvar; patterns[i].packeddata[length++] = (byte)((channel + 1) | 0x80); // write channelvar patterns[i].packeddata[length++] = maskvar; // write maskvar } else { patterns[i].packeddata[length++] = (byte)(channel + 1); } if ((maskvar & 1) == 1) { patterns[i].packeddata[length++] = (patterns[i].row[row].channel[channel].note < 0xFE) ? (byte)(patterns[i].row[row].channel[channel].note - 1) : patterns[i].row[row].channel[channel].note; } if ((maskvar & 2) == 2) { patterns[i].packeddata[length++] = patterns[i].row[row].channel[channel].instrument; } if ((maskvar & 4) == 4) { patterns[i].packeddata[length++] = (byte)(patterns[i].row[row].channel[channel].volume - 1); } if ((maskvar & 8) == 8) { patterns[i].packeddata[length++] = patterns[i].row[row].channel[channel].command; patterns[i].packeddata[length++] = patterns[i].row[row].channel[channel].parameter; } } } patterns[i].packeddata[length++] = 0; } patterns[i].length = length; Array.Resize(ref patterns[i].packeddata, length); } #endregion #region Calculate file size long filesize = 0; filesize += 0xC0 + numorders + numsamples * 4 + numpatterns * 4 + 1; messageoffset = (uint)(filesize + 1); filesize += messagelength + 1; for (int i = 0; i < numsamples; i++) { sampleoffsets[i] = (uint)filesize; filesize += 0x51; } for (int i = 0; i < numpatterns; i++) { patternoffsets[i] = (uint)filesize; filesize += patterns[i].length + 9; } for (int i = 0; i < numsamples; i++) { samples[i].samplepointer = (uint)filesize; filesize += samples[i].length * 2 + 1; } #endregion byte[] data = new byte[filesize]; #region Write the header write4(magicword, ref data, 0x00); byte[] songnamearr = System.Text.Encoding.ASCII.GetBytes(songname); for (int i = 0; i < 26; i++) { data[i + 0x04] = songnamearr[i]; } write2(pathighlight, ref data, 0x1E); write2(numorders, ref data, 0x20); write2(insnum, ref data, 0x22); write2(numsamples, ref data, 0x24); write2(numpatterns, ref data, 0x26); write2(createdwithtracker, ref data, 0x28); write2(compatablewithtracker, ref data, 0x2A); write2(flags, ref data, 0x2C); write2(special, ref data, 0x2E); data[0x30] = globalvol; data[0x31] = mixvol; data[0x32] = initialspeed; data[0x33] = initialtempo; data[0x34] = panseperation; data[0x35] = pitchwheeldepth; write2(messagelength, ref data, 0x36); write4(messageoffset, ref data, 0x38); for (int i = 0; i < messagelength; i++) { data[messageoffset + i] = message[i]; } write4(reserved, ref data, 0x3C); for (int i = 0; i < 64; i++) { data[0x40 + i] = channelpan[i]; } for (int i = 0; i < 64; i++) { data[0x80 + i] = channelvol[i]; } for (int i = 0; i < numorders; i++) { data[0xC0 + i] = orders[i]; } // ignoring instruments, since they're not supported there shouldn't be any to write for (int i = 0; i < numsamples; i++) { write4(sampleoffsets[i], ref data, 0xC0 + numorders + i * 4); } for (int i = 0; i < numpatterns; i++) { write4(patternoffsets[i], ref data, 0xC0 + numorders + numsamples * 4 + i * 4); } #endregion #region Write the sample headers for (int i = 0; i < numsamples; i++) { write4(samples[i].magicword, ref data, sampleoffsets[i] + 0x00); byte[] dosfilename = System.Text.Encoding.ASCII.GetBytes(samples[i].dosfilename); for (int j = 0; j < 12; j++) { data[sampleoffsets[i] + 0x04 + j] = dosfilename[j]; } data[sampleoffsets[i] + 0x11] = samples[i].globalvol; data[sampleoffsets[i] + 0x12] = samples[i].flags; data[sampleoffsets[i] + 0x13] = samples[i].defaultvol; byte[] samplename = System.Text.Encoding.ASCII.GetBytes(samples[i].samplename); for (int j = 0; j < 26; j++) { data[sampleoffsets[i] + 0x14 + j] = samplename[j]; } data[sampleoffsets[i] + 0x2E] = samples[i].convert; data[sampleoffsets[i] + 0x2F] = samples[i].defaultpan; write4(samples[i].length, ref data, sampleoffsets[i] + 0x30); write4(samples[i].loopbegin, ref data, sampleoffsets[i] + 0x34); write4(samples[i].loopend, ref data, sampleoffsets[i] + 0x38); write4(samples[i].c5speed, ref data, sampleoffsets[i] + 0x3C); write4(samples[i].susloopbegin, ref data, sampleoffsets[i] + 0x40); write4(samples[i].susloopend, ref data, sampleoffsets[i] + 0x44); write4(samples[i].samplepointer, ref data, sampleoffsets[i] + 0x48); data[sampleoffsets[i] + 0x4C] = samples[i].vibratospeed; data[sampleoffsets[i] + 0x4D] = samples[i].vibratodepth; data[sampleoffsets[i] + 0x4E] = samples[i].vibratorate; data[sampleoffsets[i] + 0x4F] = samples[i].vwaveformtype; for (int j = 0; j < samples[i].length * 2; j++) { data[samples[i].samplepointer + j] = samples[i].sample[j]; } } #endregion #region Write the pattern headers for (int i = 0; i < numpatterns; i++) { write2(patterns[i].length, ref data, patternoffsets[i] + 0x00); write2(patterns[i].rows, ref data, patternoffsets[i] + 0x02); for (int j = 0; j < patterns[i].length; j++) { data[patternoffsets[i] + 0x08 + j] = patterns[i].packeddata[j]; } } #endregion return(data); }
/// <summary> /// Attempts to parse the <code>byte</code> array as an it file, populating the instance of the class this function was called from with data. /// </summary> /// <param name="data"><code>byte</code> array that contains the data, which should be a complete .it file. </param> /// <throws>FormatException</throws> public void parse(byte[] data) { #region Parse the header magicword = read4(data, 0x00); if (magicword != 0x4D504D49) { throw new FormatException("Magic word mismatch. File must begin with the letters \"IMPM\". Are you sure this is an it file?"); } songname = System.Text.Encoding.ASCII.GetString(data, 0x04, 26); pathighlight = read2(data, 0x1E); numorders = read2(data, 0x20); insnum = read2(data, 0x22); if (insnum != 0) { throw new FormatException("Instruments are detected, they are not supported. This program was made for it files that were made using OpenSPC."); } numsamples = read2(data, 0x24); numpatterns = read2(data, 0x26); createdwithtracker = read2(data, 0x28); compatablewithtracker = read2(data, 0x2A); flags = read2(data, 0x2C); special = read2(data, 0x2E); globalvol = data[0x30]; mixvol = data[0x31]; initialspeed = data[0x32]; initialtempo = data[0x33]; panseperation = data[0x34]; pitchwheeldepth = data[0x35]; messagelength = read2(data, 0x36); messageoffset = read4(data, 0x38); if (messagelength > 8000) { throw new FormatException("Message length is greater than 8000 bytes."); } message = new byte[messagelength]; for (int i = 0; i < messagelength; i++) { message[i] = data[messageoffset + i]; } reserved = read4(data, 0x3C); channelpan = new byte[64]; channels = 0; for (int i = 0; i < 64; i++) { channelpan[i] = data[0x40 + i]; if (channelpan[i] < 128) { channels++; } } channelvol = new byte[64]; for (int i = 0; i < 64; i++) { channelvol[i] = data[0x80 + i]; } orders = new byte[numorders]; for (int i = 0; i < numorders; i++) { orders[i] = data[0xC0 + i]; } // ignoring instruments, since they're not supported there shouldn't be any to read sampleoffsets = new uint[numsamples]; for (int i = 0; i < numsamples; i++) { sampleoffsets[i] = read4(data, 0xC0 + numorders + i * 4); } patternoffsets = new uint[numpatterns]; for (int i = 0; i < numpatterns; i++) { patternoffsets[i] = read4(data, 0xC0 + numorders + numsamples * 4 + i * 4); } #endregion #region Parse the samples samples = new itsample[numsamples]; try { for (int i = 0; i < numsamples; i++) { samples[i].magicword = read4(data, sampleoffsets[i] + 0x00); if (samples[i].magicword != 0x53504D49) { throw new FormatException(String.Format("Magic word mismatch on sample #{0}, offset 0x{1:X}. Header must begin with the letters \"IMPS\". File may be corrupt.", i, sampleoffsets[i])); } samples[i].dosfilename = System.Text.Encoding.ASCII.GetString(data, (int)sampleoffsets[i] + 0x04, 12); samples[i].globalvol = data[sampleoffsets[i] + 0x11]; samples[i].flags = data[sampleoffsets[i] + 0x12]; samples[i].defaultvol = data[sampleoffsets[i] + 0x13]; samples[i].samplename = System.Text.Encoding.ASCII.GetString(data, (int)sampleoffsets[i] + 0x14, 26); samples[i].convert = data[sampleoffsets[i] + 0x2E]; samples[i].defaultpan = data[sampleoffsets[i] + 0x2F]; samples[i].length = read4(data, sampleoffsets[i] + 0x30); if (samples[i].length > (64 * 1024 * 1024)) { throw new FormatException(String.Format("Sample size for sample #{0} is greater than 64MB (according to the header, it's {1}MB), aborting to avoid excessive memory usage.", i, samples[i].length / 1024 / 1024)); } samples[i].loopbegin = read4(data, sampleoffsets[i] + 0x34); samples[i].loopend = read4(data, sampleoffsets[i] + 0x38); samples[i].c5speed = read4(data, sampleoffsets[i] + 0x3C); samples[i].susloopbegin = read4(data, sampleoffsets[i] + 0x40); samples[i].susloopend = read4(data, sampleoffsets[i] + 0x44); samples[i].samplepointer = read4(data, sampleoffsets[i] + 0x48); samples[i].vibratospeed = data[sampleoffsets[i] + 0x4C]; samples[i].vibratodepth = data[sampleoffsets[i] + 0x4D]; samples[i].vibratorate = data[sampleoffsets[i] + 0x4E]; samples[i].vwaveformtype = data[sampleoffsets[i] + 0x4F]; samples[i].sample = new byte[samples[i].length * 2]; for (int j = 0; j < samples[i].length * 2; j++) { samples[i].sample[j] = data[samples[i].samplepointer + j]; } } } catch (Exception e) { throw e; } #endregion #region Parse the patterns patterns = new itpattern[numpatterns]; try { for (int i = 0; i < numpatterns; i++) { patterns[i].length = read2(data, patternoffsets[i] + 0x00); patterns[i].rows = read2(data, patternoffsets[i] + 0x02); patterns[i].packeddata = new byte[patterns[i].length]; for (int j = 0; j < patterns[i].length; j++) { patterns[i].packeddata[j] = data[patternoffsets[i] + 0x08 + j]; } patterns[i].row = new rowgroup[patterns[i].rows]; for (int row = 0; row < patterns[i].rows; row++) { patterns[i].row[row].channel = new channelgroup[channels]; for (int channel = 0; channel < channels; channel++) { patterns[i].row[row].channel[channel].note = 0; patterns[i].row[row].channel[channel].instrument = 0; patterns[i].row[row].channel[channel].volume = 0; patterns[i].row[row].channel[channel].command = 0; patterns[i].row[row].channel[channel].parameter = 0; } } // now, unpack the data channelgroup[] prev = new channelgroup[64]; byte[] channelmask = new byte[64]; byte[] maskvar = new byte[64]; ushort rowsize = (ushort)(channels * 5); ushort offset = 0; byte currentchannel = 0; int currentrow = 0; try { while (currentrow < patterns[i].rows) { if (offset >= patterns[i].packeddata.Length) // sanity check { throw new FormatException(String.Format("Error parsing pattern: offset > length, pattern #{0}, offset = {1:X} length = {2:X}. The file may be corrupt.", i, offset, patterns[i].length)); //break; } byte channelvar = patterns[i].packeddata[offset++]; if (channelvar == 0) { currentrow++; continue; } if (currentrow > 255) { throw new FormatException(String.Format("Error parsing pattern: currentrow > 255, pattern #{0}. The file may be corrupt.", i)); //break; } currentchannel = (byte)((channelvar - 1) & 63); if ((channelvar & 128) == 128) // will not be updated if the highest bit is not set, will instead use whatever's already there at that position { maskvar[currentchannel] = patterns[i].packeddata[offset++]; } if ((maskvar[currentchannel] & 1) == 1) // note command { byte note = patterns[i].packeddata[offset++]; if (note < 0xFD) { note++; } //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 0 ] = note; patterns[i].row[currentrow].channel[currentchannel].note = note; prev[currentchannel].note = note; } if ((maskvar[currentchannel] & 2) == 2) // instrument command { byte instrument = patterns[i].packeddata[offset++]; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 1 ] = instrument; patterns[i].row[currentrow].channel[currentchannel].instrument = instrument; prev[currentchannel].instrument = instrument; } if ((maskvar[currentchannel] & 4) == 4) // volume command { byte volume = (byte)(patterns[i].packeddata[offset++] + 1); //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 2 ] = volume; patterns[i].row[currentrow].channel[currentchannel].volume = volume; prev[currentchannel].volume = volume; } if ((maskvar[currentchannel] & 8) == 8) // effect command { byte command = patterns[i].packeddata[offset++]; byte parameter = patterns[i].packeddata[offset++]; if (command != 0) { //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 3 ] = command; patterns[i].row[currentrow].channel[currentchannel].command = command; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 4 ] = parameter; patterns[i].row[currentrow].channel[currentchannel].parameter = parameter; prev[currentchannel].command = command; prev[currentchannel].parameter = parameter; } } if ((maskvar[currentchannel] & 16) == 16) // previous note command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 0 ] = prev[ currentchannel ].note; { patterns[i].row[currentrow].channel[currentchannel].note = prev[currentchannel].note; } if ((maskvar[currentchannel] & 32) == 32) // previous instrument command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 1 ] = prev[ currentchannel ].instrument; { patterns[i].row[currentrow].channel[currentchannel].instrument = prev[currentchannel].instrument; } if ((maskvar[currentchannel] & 64) == 64) // previous volume command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 2 ] = prev[ currentchannel ].volume; { patterns[i].row[currentrow].channel[currentchannel].volume = prev[currentchannel].volume; } if ((maskvar[currentchannel] & 128) == 128) // previous effect command //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 3 ] = prev[ currentchannel ].command; { patterns[i].row[currentrow].channel[currentchannel].command = prev[currentchannel].command; //patterns[ i ].unpackeddata[ currentrow * rowsize + currentchannel * 5 + 4 ] = prev[ currentchannel ].parameter; patterns[i].row[currentrow].channel[currentchannel].parameter = prev[currentchannel].parameter; } } } catch (Exception e) { throw e; } } } catch (Exception e) { throw e; } #endregion }
/// <summary> /// Packs the data contained within the instance of the class this was called from into a .it file, ready to be written to the disk. /// </summary> /// <returns> A <code>byte</code> array that contains a complete it file, ready to be written to the disk. </returns> public byte[] pack() { #region Pack the patterns ushort rowsize = (ushort) ( channels * 5 ); for ( int i = 0; i < numpatterns; i++ ) { channelgroup[] previousvalues = new channelgroup[ 64 ]; byte[] channelmask = new byte[ 64 ]; byte[] prevmaskvar = new byte[ 64 ]; ushort length = 0; patterns[ i ].packeddata = new byte[ 256 * 64 * 7 ]; for ( int row = 0; row < patterns[ i ].rows; row++ ) { for ( int channel = 0; channel < channels; channel++ ) { byte maskvar = 0; if ( patterns[ i ].row[ row ].channel[ channel ].note != 0 ) maskvar |= 1; if ( patterns[ i ].row[ row ].channel[ channel ].instrument != 0 ) maskvar |= 2; if ( patterns[ i ].row[ row ].channel[ channel ].volume != 0 ) maskvar |= 4; if ( patterns[ i ].row[ row ].channel[ channel ].command != 0 ) maskvar |= 8; if ( maskvar != 0 ) { if ( ( maskvar & 1 ) == 1 ) { if ( patterns[ i ].row[ row ].channel[ channel ].note == previousvalues[ channel ].note ) { unchecked { maskvar &= (byte) ~0x01; maskvar |= 0x10; } } else { previousvalues[ channel ].note = patterns[ i ].row[ row ].channel[ channel ].note; } } if ( ( maskvar & 2 ) == 2 ) { if ( patterns[ i ].row[ row ].channel[ channel ].instrument == previousvalues[ channel ].instrument ) { unchecked { maskvar &= (byte) ~0x02; maskvar |= 0x20; } } else { previousvalues[ channel ].instrument = patterns[ i ].row[ row ].channel[ channel ].instrument; } } if ( ( maskvar & 4 ) == 4 ) { if ( patterns[ i ].row[ row ].channel[ channel ].volume == previousvalues[ channel ].volume ) { unchecked { maskvar &= (byte) ~0x04; maskvar |= 0x40; } } else { previousvalues[ channel ].volume = patterns[ i ].row[ row ].channel[ channel ].volume; } } if ( ( maskvar & 8 ) == 8 ) { if ( ( patterns[ i ].row[ row ].channel[ channel ].command == previousvalues[ channel ].command ) && ( patterns[ i ].row[ row ].channel[ channel ].parameter == previousvalues[ channel ].parameter ) ) { unchecked { maskvar &= (byte) ~0x08; maskvar |= 0x80; } } else { previousvalues[ channel ].command = patterns[ i ].row[ row ].channel[ channel ].command; previousvalues[ channel ].parameter = patterns[ i ].row[ row ].channel[ channel ].parameter; } } if ( maskvar != channelmask[ channel ] ) { channelmask[ channel ] = maskvar; patterns[ i ].packeddata[ length++ ] = (byte) ( ( channel + 1 ) | 0x80 ); // write channelvar patterns[ i ].packeddata[ length++ ] = maskvar; // write maskvar } else { patterns[ i ].packeddata[ length++ ] = (byte) ( channel + 1 ); } if ( ( maskvar & 1 ) == 1 ) patterns[ i ].packeddata[ length++ ] = ( patterns[ i ].row[ row ].channel[ channel ].note < 0xFE ) ? (byte) ( patterns[ i ].row[ row ].channel[ channel ].note - 1 ) : patterns[ i ].row[ row ].channel[ channel ].note; if ( ( maskvar & 2 ) == 2 ) patterns[ i ].packeddata[ length++ ] = patterns[ i ].row[ row ].channel[ channel ].instrument; if ( ( maskvar & 4 ) == 4 ) patterns[ i ].packeddata[ length++ ] = (byte) ( patterns[ i ].row[ row ].channel[ channel ].volume - 1 ); if ( ( maskvar & 8 ) == 8 ) { patterns[ i ].packeddata[ length++ ] = patterns[ i ].row[ row ].channel[ channel ].command; patterns[ i ].packeddata[ length++ ] = patterns[ i ].row[ row ].channel[ channel ].parameter; } } } patterns[ i ].packeddata[ length++ ] = 0; } patterns[ i ].length = length; Array.Resize( ref patterns[ i ].packeddata, length ); } #endregion #region Calculate file size long filesize = 0; filesize += 0xC0 + numorders + numsamples * 4 + numpatterns * 4 + 1; messageoffset = (uint) ( filesize + 1 ); filesize += messagelength + 1; for ( int i = 0; i < numsamples; i++ ) { sampleoffsets[ i ] = (uint) filesize; filesize += 0x51; } for ( int i =0; i < numpatterns; i++ ) { patternoffsets[ i ] = (uint) filesize; filesize += patterns[ i ].length + 9; } for ( int i = 0; i < numsamples; i++ ) { samples[ i ].samplepointer = (uint) filesize; filesize += samples[ i ].length * 2 + 1; } #endregion byte[] data = new byte[ filesize ]; #region Write the header write4( magicword, ref data, 0x00 ); byte[] songnamearr = System.Text.Encoding.ASCII.GetBytes( songname ); for ( int i = 0; i < 26; i++ ) data[ i + 0x04 ] = songnamearr[ i ]; write2( pathighlight, ref data, 0x1E ); write2( numorders, ref data, 0x20 ); write2( insnum, ref data, 0x22 ); write2( numsamples, ref data, 0x24 ); write2( numpatterns, ref data, 0x26 ); write2( createdwithtracker, ref data, 0x28 ); write2( compatablewithtracker, ref data, 0x2A ); write2( flags, ref data, 0x2C ); write2( special, ref data, 0x2E ); data[ 0x30 ] = globalvol; data[ 0x31 ] = mixvol; data[ 0x32 ] = initialspeed; data[ 0x33 ] = initialtempo; data[ 0x34 ] = panseperation; data[ 0x35 ] = pitchwheeldepth; write2( messagelength, ref data, 0x36 ); write4( messageoffset, ref data, 0x38 ); for ( int i = 0; i < messagelength; i++ ) { data[ messageoffset + i ] = message[ i ]; } write4( reserved, ref data, 0x3C ); for ( int i = 0; i < 64; i++ ) { data[ 0x40 + i ] = channelpan[ i ]; } for ( int i = 0; i < 64; i++ ) { data[ 0x80 + i ] = channelvol[ i ]; } for ( int i = 0; i < numorders; i++ ) { data[ 0xC0 + i ] = orders[ i ]; } // ignoring instruments, since they're not supported there shouldn't be any to write for ( int i = 0; i < numsamples; i++ ) { write4( sampleoffsets[ i ], ref data, 0xC0 + numorders + i * 4 ); } for ( int i = 0; i < numpatterns; i++ ) { write4( patternoffsets[ i ], ref data, 0xC0 + numorders + numsamples * 4 + i * 4 ); } #endregion #region Write the sample headers for ( int i = 0; i < numsamples; i++ ) { write4( samples[ i ].magicword, ref data, sampleoffsets[ i ] + 0x00 ); byte[] dosfilename = System.Text.Encoding.ASCII.GetBytes( samples[ i ].dosfilename ); for ( int j = 0; j < 12; j++ ) { data[ sampleoffsets[ i ] + 0x04 + j ] = dosfilename[ j ]; } data[ sampleoffsets[ i ] + 0x11 ] = samples[ i ].globalvol; data[ sampleoffsets[ i ] + 0x12 ] = samples[ i ].flags; data[ sampleoffsets[ i ] + 0x13 ] = samples[ i ].defaultvol; byte[] samplename = System.Text.Encoding.ASCII.GetBytes( samples[ i ].samplename ); for ( int j = 0; j < 26; j++ ) { data[ sampleoffsets[ i ] + 0x14 + j ] = samplename[ j ]; } data[ sampleoffsets[ i ] + 0x2E ] = samples[ i ].convert; data[ sampleoffsets[ i ] + 0x2F ] = samples[ i ].defaultpan; write4( samples[ i ].length, ref data, sampleoffsets[ i ] + 0x30 ); write4( samples[ i ].loopbegin, ref data, sampleoffsets[ i ] + 0x34 ); write4( samples[ i ].loopend, ref data, sampleoffsets[ i ] + 0x38 ); write4( samples[ i ].c5speed, ref data, sampleoffsets[ i ] + 0x3C ); write4( samples[ i ].susloopbegin, ref data, sampleoffsets[ i ] + 0x40 ); write4( samples[ i ].susloopend, ref data, sampleoffsets[ i ] + 0x44 ); write4( samples[ i ].samplepointer, ref data, sampleoffsets[ i ] + 0x48 ); data[ sampleoffsets[ i ] + 0x4C ] = samples[ i ].vibratospeed; data[ sampleoffsets[ i ] + 0x4D ] = samples[ i ].vibratodepth; data[ sampleoffsets[ i ] + 0x4E ] = samples[ i ].vibratorate; data[ sampleoffsets[ i ] + 0x4F ] = samples[ i ].vwaveformtype; for ( int j = 0; j < samples[ i ].length * 2; j++ ) { data[ samples[ i ].samplepointer + j ] = samples[ i ].sample[ j ]; } } #endregion #region Write the pattern headers for ( int i = 0; i < numpatterns; i++ ) { write2( patterns[ i ].length, ref data, patternoffsets[ i ] + 0x00 ); write2( patterns[ i ].rows, ref data, patternoffsets[ i ] + 0x02 ); for ( int j = 0; j < patterns[ i ].length; j++ ) { data[ patternoffsets[ i ] + 0x08 + j ] = patterns[ i ].packeddata[ j ]; } } #endregion return data; }