#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
}
}