#region License /* Copyright (c) 2005 Leslie Sanford * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to * deal in the Software without restriction, including without limitation the * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or * sell copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #endregion #region Contact /* * Leslie Sanford * Email: jabberdabber@hotmail.com */ #endregion using System; using System.ComponentModel; using System.Diagnostics; namespace Sanford.Multimedia.Midi { #region Meta Message Types /// /// Represents MetaMessage types. /// public enum MetaType { /// /// Represents sequencer number type. /// SequenceNumber, /// /// Represents the text type. /// Text, /// /// Represents the copyright type. /// Copyright, /// /// Represents the track name type. /// TrackName, /// /// Represents the instrument name type. /// InstrumentName, /// /// Represents the lyric type. /// Lyric, /// /// Represents the marker type. /// Marker, /// /// Represents the cue point type. /// CuePoint, /// /// Represents the program name type. /// ProgramName, /// /// Represents the device name type. /// DeviceName, /// /// Represents then end of track type. /// EndOfTrack = 0x2F, /// /// Represents the tempo type. /// Tempo = 0x51, /// /// Represents the Smpte offset type. /// SmpteOffset = 0x54, /// /// Represents the time signature type. /// TimeSignature = 0x58, /// /// Represents the key signature type. /// KeySignature, /// /// Represents the proprietary event type. /// ProprietaryEvent = 0x7F } #endregion /// /// Represents MIDI meta messages. /// /// /// Meta messages are MIDI messages that are stored in MIDI files. These /// messages are not sent or received via MIDI but are read and /// interpretted from MIDI files. They provide information that describes /// a MIDI file's properties. For example, tempo changes are implemented /// using meta messages. /// [ImmutableObject(true)] public sealed class MetaMessage : IMidiMessage { #region MetaMessage Members #region Constants /// /// The amount to shift data bytes when calculating the hash code. /// private const int Shift = 7; // // Meta message length constants. // /// /// Length in bytes for tempo meta message data. /// public const int TempoLength = 3; /// /// Length in bytes for SMPTE offset meta message data. /// public const int SmpteOffsetLength = 5; /// /// Length in bytes for time signature meta message data. /// public const int TimeSigLength = 4; /// /// Length in bytes for key signature meta message data. /// public const int KeySigLength = 2; #endregion #region Class Fields /// /// End of track meta message. /// public static readonly MetaMessage EndOfTrackMessage = new MetaMessage(MetaType.EndOfTrack, new byte[0]); #endregion #region Fields // The meta message type. private MetaType type; // The meta message data. private byte[] data; // The hash code value. private int hashCode; #endregion #region Construction /// /// Initializes a new instance of the MetaMessage class. /// /// /// The type of MetaMessage. /// /// /// The MetaMessage data. /// /// /// The length of the MetaMessage is not valid for the MetaMessage type. /// /// /// Each MetaMessage has type and length properties. For certain /// types, the length of the message data must be a specific value. For /// example, tempo messages must have a data length of exactly three. /// Some MetaMessage types can have any data length. Text messages are /// an example of a MetaMessage that can have a variable data length. /// When a MetaMessage is created, the length of the data is checked /// to make sure that it is valid for the specified type. If it is not, /// an exception is thrown. /// public MetaMessage(MetaType type, byte[] data) { #region Require if(data == null) { throw new ArgumentNullException("data"); } else if(!ValidateDataLength(type, data.Length)) { throw new ArgumentException( "Length of data not valid for meta message type."); } #endregion this.type = type; // Create storage for meta message data. this.data = new byte[data.Length]; // Copy data into storage. data.CopyTo(this.data, 0); CalculateHashCode(); } #endregion #region Methods /// /// Gets a copy of the data bytes for this meta message. /// /// /// A copy of the data bytes for this meta message. /// public byte[] GetBytes() { return (byte[])data.Clone(); } /// /// Returns a value for the current MetaMessage suitable for use in /// hashing algorithms. /// /// /// A hash code for the current MetaMessage. /// public override int GetHashCode() { return hashCode; } /// /// Determines whether two MetaMessage instances are equal. /// /// /// The MetaMessage to compare with the current MetaMessage. /// /// /// true if the specified MetaMessage is equal to the current /// MetaMessage; otherwise, false. /// public override bool Equals(object obj) { #region Guard if(!(obj is MetaMessage)) { return false; } #endregion bool equal = true; MetaMessage message = (MetaMessage)obj; // If the types do not match. if(MetaType != message.MetaType) { // The messages are not equal equal = false; } // If the message lengths are not equal. if(equal && Length != message.Length) { // The message are not equal. equal = false; } // Check to see if the data is equal. for(int i = 0; i < Length && equal; i++) { // If a data value does not match. if(this[i] != message[i]) { // The messages are not equal. equal = false; } } return equal; } // Calculates the hash code. private void CalculateHashCode() { // TODO: This algorithm may need work. hashCode = (int)MetaType; for(int i = 0; i < data.Length; i += 3) { hashCode ^= data[i]; } for(int i = 1; i < data.Length; i += 3) { hashCode ^= data[i] << Shift; } for(int i = 2; i < data.Length; i += 3) { hashCode ^= data[i] << Shift * 2; } } /// /// Validates data length. /// /// /// The MetaMessage type. /// /// /// The length of the MetaMessage data. /// /// /// true if the data length is valid for this type of /// MetaMessage; otherwise, false. /// private bool ValidateDataLength(MetaType type, int length) { #region Require Debug.Assert(length >= 0); #endregion bool result = true; // Determine which type of meta message this is and check to make // sure that the data length value is valid. switch(type) { case MetaType.SequenceNumber: if(length != 0 || length != 2) { result = false; } break; case MetaType.EndOfTrack: if(length != 0) { result = false; } break; case MetaType.Tempo: if(length != TempoLength) { result = false; } break; case MetaType.SmpteOffset: if(length != SmpteOffsetLength) { result = false; } break; case MetaType.TimeSignature: if(length != TimeSigLength) { result = false; } break; case MetaType.KeySignature: if(length != KeySigLength) { result = false; } break; default: result = true; break; } return result; } #endregion #region Properties /// /// Gets the element at the specified index. /// /// /// index is less than zero or greater than or equal to Length. /// public byte this[int index] { get { #region Require if(index < 0 || index >= Length) { throw new ArgumentOutOfRangeException("index", index, "Index into MetaMessage out of range."); } #endregion return data[index]; } } /// /// Gets the length of the meta message. /// public int Length { get { return data.Length; } } /// /// Gets the type of meta message. /// public MetaType MetaType { get { return type; } } #endregion #endregion #region IMidiMessage Members /// /// Gets the status value. /// public int Status { get { // All meta messages have the same status value (0xFF). return 0xFF; } } /// /// Gets the MetaMessage's MessageType. /// public MessageType MessageType { get { return MessageType.Meta; } } #endregion } }