Click here to Skip to main content
15,886,362 members
Articles / Programming Languages / C#

Steganography V - Hiding Messages in MIDI Songs

Rate me:
Please Sign up or sign in to vote.
4.94/5 (20 votes)
5 Aug 2004CDDL6 min read 133.7K   2.4K   47  
An article about hiding bytes in the Program Change events of a MIDI file
using System;
using System.IO;
using System.Windows.Forms;
using System.Collections;

namespace SteganoMidi
{
	/// <summary>Analyse a MIDI file</summary>
	public class MidiFileReader {

		public void ReadFile(String srcFileName, out String report, out int countProgramChange, out long fileSize){
			FileStream srcFile = new FileStream(srcFileName, FileMode.Open);
			BinaryReader srcReader = new BinaryReader(srcFile);
			
			report = String.Empty;
			fileSize = 0;
			countProgramChange = 0;
			int countNoteOff = 0;
			int countNoteOn = 0;
			int countAfterTouch = 0;
			int countChannelPressure = 0;
			int countControlChange = 0;
			int countPitchWheel = 0;
			int countSysEx = 0;
			int countNonMidi = 0;

			try{

				MidiFileHeader header = new MidiFileHeader();

				//Read type
				header.HeaderType = srcReader.ReadChars(4);
				header.DataLength = new byte[4];
				header.DataLength = srcReader.ReadBytes(4);
			
				if((new String(header.HeaderType) != "MThd")
					||(header.DataLength[3] != 6)){
					MessageBox.Show("This is not a standard MIDI file!");
					srcReader.Close();
				}
			
				//These values are Int16, stored in reverse byte order
				header.FileType = (Int16)(srcReader.ReadByte()*16 + srcReader.ReadByte());
				header.CountTracks = (Int16)(srcReader.ReadByte()*16 + srcReader.ReadByte());
				header.Division = (Int16)(srcReader.ReadByte()*16 + srcReader.ReadByte());
				
				//-------------------------------- Read Tracks

				MidiMessage midiMessage = new MidiMessage();
				midiMessage.MessageData = new byte[2];
								
				for(int track=0; track<header.CountTracks; track++){

					if(srcReader.BaseStream.Position == srcReader.BaseStream.Length){
						//no more tracks found
						break;
					}
				
					//Read track header
				
					MidiTrackHeader th = new MidiTrackHeader();
					th.HeaderType = srcReader.ReadChars(4);
					if(new String(th.HeaderType) != "MTrk"){
						//not a standard track - search the next track
						while(srcReader.BaseStream.Position+8 < srcReader.BaseStream.Length){
							th.HeaderType = srcReader.ReadChars(4);
							if(new String(th.HeaderType) == "MTrk"){
								break;
							}
						}
					}
				
					//Read the length field and convert it to Int32
					//srcReader.ReadInt32() returns a wrong value,
					//because of the reverse byte order					
					byte[] trackLength = srcReader.ReadBytes(4);

					th.DataLength = trackLength[3]
						+ trackLength[2]*256
						+ trackLength[1]*4096
						+ trackLength[0]*65536;
					
					long startOfTrack = srcReader.BaseStream.Position;
					bool isEndOfTrack = false;
					//while((srcReader.BaseStream.Position - startOfTrack) < th.DataLength){
					while( ! isEndOfTrack){
			
						//Read the messages

						/* 1st field: Time - variable length
						 * 2nd fiels: Message type and channel - 1 byte
						 *    The lower four bits contain channel (0-15),
						 *    the higher four bits contain the message type (8-F)
						 * 3rd and 4th field: Message parameters - 1 byte each */

						//Read time
						ReadVariableLengthValue(srcReader, out midiMessage.Time);
						//Read type and channel
						midiMessage.MessageType = srcReader.ReadByte();
						
						if(midiMessage.MessageType == 0xFF){ //non-MIDI event
							byte name = srcReader.ReadByte();
							int length = (int)ReadVariableLengthValue(srcReader, out midiMessage.MessageData);
							srcReader.ReadBytes(length);
							countNonMidi++;

							if((name == 0x2F)&&(length == 0)){ // End Of Track
								break; //continue with next track
							}
						}else{
				
							//remove channel information by resetting the 4 lower bits
							byte cleanMessageType = midiMessage.MessageType;
							byte[] bitValues = {1, 2, 4, 8};
							foreach(byte bitValue in bitValues){
								if((cleanMessageType & bitValue) > 0){
									cleanMessageType = (byte)(cleanMessageType ^ bitValue);
								}
							}

							switch(cleanMessageType){
								case 0x80:{ //Note Off - Note and Velocity following
									midiMessage.MessageData = srcReader.ReadBytes(2);
									countNoteOff++;
									break;
								}
								case 0x90:{ //Note On - Note and Velocity following
									midiMessage.MessageData = srcReader.ReadBytes(2);
									countNoteOn++;
									break;
								}
								case 0xA0:{ //After Touch - Note and Pressure following
									srcReader.ReadBytes(2);
									countAfterTouch++;
									break;
								}
								case 0xB0:{ //Control Change - Control and Value following
									srcReader.ReadBytes(2);
									countControlChange++;
									break;
								}
								case 0xC0:{ //Program Change - Program following
									midiMessage.MessageData = new  byte[1]{ srcReader.ReadByte() };
									countProgramChange++;
									break;
								}
								case 0xD0:{ //Channel Pressure - Value following
									srcReader.ReadByte();
									countChannelPressure++;
									break;
								}
								case 0xE0:{ //Pitch Wheel - 14-bit value following
									srcReader.ReadBytes(2);
									countPitchWheel++;
									break;
								}
								case 0xF0: { //SysEx - no static length, read until end tag 0xF7 is found
									byte b=0;
									while(b != 0xF7){ b = srcReader.ReadByte(); }
									countSysEx++;
									break;
								}
							} 
						} //else - MIDI message

					} //while() over messages

				}//for() over tracks

				//Fill return variables
				fileSize = srcFile.Length;
				report += "---- MIDI Messages ----\r\n";
				report += "Program Change: "+countProgramChange+"\r\n";
				report += "Note Off: "+countNoteOff+"\r\n";
				report += "Note On: "+countNoteOn+"\r\n";
				report += "After Touch: "+countAfterTouch+"\r\n";
				report += "Channel Pressure: "+countChannelPressure+"\r\n";
				report += "Control Change: "+countControlChange+"\r\n";
				report += "PitchWheel: "+countPitchWheel+"\r\n";
				report += "System Exclusive Messages: "+countSysEx+"\r\n";
				report += "Non-Midi Messages: "+countNonMidi+"\r\n";
				
			}catch(Exception ex){
				MessageBox.Show("This is not a standard MIDI file!\r\n\r\n" + ex.ToString());
			}finally{
				srcReader.Close();
			}
		}

		private static long ReadVariableLengthValue(BinaryReader srcReader, out byte[] rawDataBuffer) {
			long returnValue;
			byte b;
			ArrayList allBytes = new ArrayList();

			//read the first byte
			returnValue = srcReader.ReadByte();
			allBytes.Add((byte)returnValue);
			
			if ( (returnValue & 0x80) > 0 ) { //bit 7 is set: there are more bytes to read
				returnValue &= 0x7F; //remove bit 7 - it is only a not-the-last-one flag
				do {
					b = srcReader.ReadByte(); //read next byte
					allBytes.Add(b);
					//remove flag and append byte
					returnValue = (returnValue << 7) + (b & 0x7F);
				} while( (b & 0x80) > 0 ); //until bit-7 is not set
			}

			rawDataBuffer = (byte[])allBytes.ToArray(typeof(byte));
			return returnValue;
		}

	}
}

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The Common Development and Distribution License (CDDL)


Written By
Software Developer
Germany Germany
Corinna lives in Hanover/Germany and works as a C# developer.

Comments and Discussions