Path: blob/master/Sanford.Multimedia.Midi/Sequencing/MidiFileProperties.cs
180 views
#region License /* Copyright (c) 2006 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: [email protected] */ #endregion using System; using System.Diagnostics; using System.IO; namespace Sanford.Multimedia.Midi { /// <summary> /// Defintes constants representing SMPTE frame rates. /// </summary> public enum SmpteFrameRate { Smpte24 = 24, Smpte25 = 25, Smpte30Drop = 29, Smpte30 = 30 } /// <summary> /// The different types of sequences. /// </summary> public enum SequenceType { Ppqn, Smpte } /// <summary> /// Represents MIDI file properties. /// </summary> internal class MidiFileProperties { private const int PropertyLength = 2; private static readonly byte[] MidiFileHeader = { (byte)'M', (byte)'T', (byte)'h', (byte)'d', 0, 0, 0, 6 }; private int format = 1; private int trackCount = 0; private int division = PpqnClock.PpqnMinValue; private SequenceType sequenceType = SequenceType.Ppqn; public MidiFileProperties() { } public void Read(Stream strm) { #region Require if(strm == null) { throw new ArgumentNullException("strm"); } #endregion format = trackCount = division = 0; FindHeader(strm); Format = (int)ReadProperty(strm); TrackCount = (int)ReadProperty(strm); Division = (int)ReadProperty(strm); #region Invariant AssertValid(); #endregion } private void FindHeader(Stream stream) { bool found = false; int result; while(!found) { result = stream.ReadByte(); if(result == 'M') { result = stream.ReadByte(); if(result == 'T') { result = stream.ReadByte(); if(result == 'h') { result = stream.ReadByte(); if(result == 'd') { found = true; } } } } if(result < 0) { throw new MidiFileException("Unable to find MIDI file header."); } } // Eat the header length. for(int i = 0; i < 4; i++) { if(stream.ReadByte() < 0) { throw new MidiFileException("Unable to find MIDI file header."); } } } private ushort ReadProperty(Stream strm) { byte[] data = new byte[PropertyLength]; int result = strm.Read(data, 0, data.Length); if(result != data.Length) { throw new MidiFileException("End of MIDI file unexpectedly reached."); } if(BitConverter.IsLittleEndian) { Array.Reverse(data); } return BitConverter.ToUInt16(data, 0); } public void Write(Stream strm) { #region Require if(strm == null) { throw new ArgumentNullException("strm"); } #endregion strm.Write(MidiFileHeader, 0, MidiFileHeader.Length); WriteProperty(strm, (ushort)Format); WriteProperty(strm, (ushort)TrackCount); WriteProperty(strm, (ushort)Division); } private void WriteProperty(Stream strm, ushort property) { byte[] data = BitConverter.GetBytes(property); if(BitConverter.IsLittleEndian) { Array.Reverse(data); } strm.Write(data, 0, PropertyLength); } private static bool IsSmpte(int division) { bool result; byte[] data = BitConverter.GetBytes((short)division); if(BitConverter.IsLittleEndian) { Array.Reverse(data); } if((sbyte)data[0] < 0) { result = true; } else { result = false; } return result; } [Conditional("DEBUG")] private void AssertValid() { if(trackCount > 1) { Debug.Assert(Format == 1 || Format == 2); } if(IsSmpte(Division)) { Debug.Assert(SequenceType == SequenceType.Smpte); } else { Debug.Assert(SequenceType == SequenceType.Ppqn); Debug.Assert(Division % PpqnClock.PpqnMinValue == 0); } } public int Format { get { return format; } set { #region Require if(value < 0 || value > 3) { throw new ArgumentOutOfRangeException("Format", value, "MIDI file format out of range."); } else if(value == 0 && trackCount > 1) { throw new ArgumentException( "MIDI file format invalid for this track count."); } #endregion format = value; #region Invariant AssertValid(); #endregion } } public int TrackCount { get { return trackCount; } set { #region Require if(value < 0) { throw new ArgumentOutOfRangeException("TrackCount", value, "Track count out of range."); } else if(value > 1 && Format == 0) { throw new ArgumentException( "Track count invalid for this format."); } #endregion trackCount = value; #region Invariant AssertValid(); #endregion } } public int Division { get { return division; } set { if(IsSmpte(value)) { byte[] data = BitConverter.GetBytes((short)value); if(BitConverter.IsLittleEndian) { Array.Reverse(data); } if((sbyte)data[0] != -(int)SmpteFrameRate.Smpte24 && (sbyte)data[0] != -(int)SmpteFrameRate.Smpte25 && (sbyte)data[0] != -(int)SmpteFrameRate.Smpte30 && (sbyte)data[0] != -(int)SmpteFrameRate.Smpte30Drop) { throw new ArgumentException("Invalid SMPTE frame rate."); } else { sequenceType = SequenceType.Smpte; } } else { if(value % PpqnClock.PpqnMinValue != 0) { throw new ArgumentException( "Invalid pulses per quarter note value."); } else { sequenceType = SequenceType.Ppqn; } } division = value; #region Invariant AssertValid(); #endregion } } public SequenceType SequenceType { get { return sequenceType; } } } public class MidiFileException : ApplicationException { public MidiFileException(string message) : base(message) { } } }