Click here to Skip to main content
Click here to Skip to main content
 
Add your own
alternative version

C# MIDI Toolkit

, 18 Apr 2007 MIT
A toolkit for creating MIDI applications with C#.
csharpmiditoolkitv4_demo.zip
CSharpMidiToolkitV4_demo
LSCollections.dll
MidiToolkitDemo
Properties
Settings.settings
MidiWatcher
Properties
Settings.settings
Multimedia.dll
Multimedia.Midi
Clocks
Device Classes
InputDevice Class
OutputDevice Classes
Messages
Message Builders
Multimedia.Midi.csproj.user
Processing
Sequencing
Midi File Player
Track Classes
UI
StateMachineToolkit.dll
csharpmiditoolkitv5_demo.zip
CSharpMidiToolkitV5_demo
MidiWatcher
Properties
Settings.settings
Sanford.Collections.dll
Sanford.Multimedia.dll
Sanford.Multimedia.Midi
Clocks
Device Classes
InputDevice Class
OutputDevice Classes
Messages
EventArgs
Message Builders
Processing
Sanford.Multimedia.Midi.csproj.user
Sequencing
Track Classes
UI
Sanford.Multimedia.Timers.dll
Sanford.Threading.dll
SequencerDemo
Properties
Settings.settings
miditoolkit_demo.zip
CSharpMidiToolkit
MidiToolkitDemo
App.ico
MidiToolkitDemo.csproj.user
Multimedia
Multimedia.csproj.user
Multimedia.Midi
Devices
Messages
Message Adaptors
MIDI File
Multimedia.MIDI.csproj.user
Sequencing
Synchronization
UI
miditoolkit_src.zip
MidiToolkit_src
Devices
Messages
MIDI File
Sequencing
TickGenerators
UI
/*
 * Created by: Leslie Sanford
 * 
 * Contact: jabberdabber@hotmail.com
 * 
 * Last modified: 11/01/2004
 */

using System;
using System.Collections;

namespace Multimedia.Midi
{
	/// <summary>
	/// Represents a collection of Tracks.
	/// </summary>
	public class Sequence : IEnumerable
	{
        #region Sequence Members

        #region Constants

        // Default division resolution.
        private const int DefaultDivision = 96;

        // Number of bits to shift for splitting division value.
        private const int DivisionShift = 8;

        #endregion

        #region Fields

        // The resolution of the sequence.
        private int division;

        // The length of the sequence in ticks.
        private int length = 0;

        // The collection of tracks for the sequence.
        private ArrayList tracks = new ArrayList();

        // The collection of objects that have locked the sequence to keep it
        // from being modified.
        private ArrayList lockers = ArrayList.Synchronized(new ArrayList());

        // The current sequence version.
        private int version = 0;

        // The previous sequence version.
        private int prevVersion = 0;

        #endregion        

        #region Construction

        /// <summary>
        /// Initializes a new instance of the Sequence class.
        /// </summary>
        public Sequence()
        {
            division = DefaultDivision;
        }

        /// <summary>
        /// Initializes a new instance of the Sequence class with the 
        /// specified division.
        /// </summary>
        /// <param name="division">
        /// The division value for the sequence.
        /// </param>
		public Sequence(int division)
		{
            this.division = division;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Adds a track to the Sequence.
        /// </summary>
        /// <param name="trk">
        /// The track to add to the Sequence.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the sequence is currently locked.
        /// </exception>
        public void Add(Track trk)
        {
            // Enforce preconditions.
            if(IsLocked())
                throw new InvalidOperationException(
                    "Cannot modify sequence. It is currently locked.");

            tracks.Add(new DictionaryEntry(trk, trk.Version));

            version++;
        }

        /// <summary>
        /// Removes the specified track from the sequence.
        /// </summary>
        /// <param name="trk">
        /// The track to remove.
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the sequence is currently locked.
        /// </exception>
        public void Remove(Track trk)
        {
            // Enforce preconditions.
            if(IsLocked())
                throw new InvalidOperationException(
                    "Cannot modify sequence. It is currently locked.");

            int i = 0;

            while(i < tracks.Count)
            {
                DictionaryEntry de = (DictionaryEntry)tracks[i];

                if((Track)de.Key == trk)
                {
                    tracks.RemoveAt(i);
                    break;
                }
                else
                {
                    i++;
                }
            }

            version++;
        }

        /// <summary>
        /// Remove the Track at the specified index.
        /// </summary>
        /// <param name="index">
        /// The zero-based index of the Track to remove. 
        /// </param>
        /// <exception cref="InvalidOperationException">
        /// Thrown if the sequence is currently locked.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if index is less than zero or greater or equal to Count.
        /// </exception>
        public void RemoveAt(int index)
        {
            // Enforce preconditions.
            if(IsLocked())
                throw new InvalidOperationException(
                    "Cannot modify sequence. It is currently locked.");
            else if(index < 0 || index >= Count)
                throw new ArgumentOutOfRangeException("index", index,
                    "Index for removing track from sequence out of range.");

            tracks.RemoveAt(index);

            version++;
        }

        /// <summary>
        /// Determines whether or not the sequence is locked.
        /// </summary>
        /// <returns>
        /// <b>true</b> if the sequence is locked; otherwise, <b>false</b>.
        /// </returns>
        /// <remarks>
        /// A sequence is locked when a sequencer is playing it. Attempting to
        /// change a sequence while it is locked will result in an exception 
        /// being thrown.
        /// </remarks>
        public bool IsLocked()
        {
            return lockers.Count > 0;
        } 

        /// <summary>
        /// Determines whether or not this is a Smpte sequence.
        /// </summary>
        /// <returns>
        /// <b>true</b> if this is a Smpte sequence; otherwise, <b>false</b>.
        /// </returns>
        public bool IsSmpte()
        {
            bool result = false;

            // The upper byte of the division value will be negative if this is
            // a SMPTE sequence.
            if((sbyte)(division >> DivisionShift) < 0)
            {
                result = true;
            }

            return result;
        }

        /// <summary>
        /// Locks a sequence to keep it from being modified.
        /// </summary>
        /// <param name="locker">
        /// The object locking the sequence.
        /// </param>
        /// <param name="lockSequence">
        /// A value indicating whether or not to lock the sequence.
        /// </param>
        internal void LockSequence(object locker, bool lockSequence)
        {
            if(lockSequence)
                lockers.Add(locker);
            else
                lockers.Remove(locker);

            foreach(DictionaryEntry de in tracks)
            {
                Track t = (Track)de.Key;
                t.LockTrack(locker, lockSequence);
            }
        }
        
        #endregion
   
        #region Properties

        /// <summary>
        /// Gets or sets the Track at the specified index.
        /// </summary>
        /// <exception cref="InvalidOperationException">
        /// Thrown if an attempt is made to set a track at the specified index
        /// and the sequence is currently locked.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Thrown if index is less than zero or greater or equal to Count.
        /// </exception>
        public Track this[int index]
        {
            get
            {
                // Enforce preconditions.
                if(index < 0 || index >= Count)
                    throw new ArgumentOutOfRangeException("index", index,
                        "Index into sequence out of range.");

                DictionaryEntry de = (DictionaryEntry)tracks[index];

                return (Track)de.Key;
            }
            set
            {
                // Enforce preconditions.
                if(IsLocked())
                    throw new InvalidOperationException(
                        "Cannot modify sequence. It is currently locked.");
                else if(index < 0 || index >= Count)
                    throw new ArgumentOutOfRangeException("index", index,
                        "Index into sequence out of range.");                

                tracks[index] = new DictionaryEntry(value, value.Version);

                version++;
            }
        }

        /// <summary>
        /// Gets the number of Tracks in the Sequence.
        /// </summary>
        public int Count
        {
            get
            {
                return tracks.Count;
            }
        }

        /// <summary>
        /// Gets the division value for the Sequence.
        /// </summary>
        public int Division
        {
            get
            {
                return division;
            }
        }

        /// <summary>
        /// Gets the length of the sequence in ticks.
        /// </summary>
        public int Length
        {
            get
            {
                if(prevVersion != Version)
                {
                    length = 0;

                    foreach(DictionaryEntry de in tracks)
                    {
                        Track t = (Track)de.Key; 

                        if(length < t.Length)
                            length = t.Length;
                    }

                    prevVersion = Version;
                }

                return length;
            }
        }

        /// <summary>
        /// Gets a value representing the version of the sequence.
        /// </summary>
        internal int Version
        {
            get
            {
                for(int i = 0; i < tracks.Count; i++)
                {
                    DictionaryEntry de = (DictionaryEntry)tracks[i];
                    Track t = (Track)de.Key;

                    if(t.Version != (int)de.Value)
                    {
                        tracks[i] = new DictionaryEntry(t, t.Version);
                        version++;
                    }
                }

                return version;
            }
        }

        #endregion

        #endregion

        #region IEnumerable Members

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public IEnumerator GetEnumerator()
        {
            return new SequenceEnumerator(this);
        }

        #endregion

        #region SequenceEnumerator Class

        /// <summary>
        /// Provides enumeration for the Sequence class.
        /// </summary>
        private class SequenceEnumerator : IEnumerator
        {
            #region SequenceEnumerator Members

            #region Fields

            // The sequence to iterate over.
            private Sequence owner;

            // The sequence version - used to make sure the sequence has not 
            // been modified since the creation of the enumerator.
            private int version;

            // Track index.
            private int trackIndex = -1;

            #endregion

            #region Construction

            /// <summary>
            /// Initializes a new instance of the SequenceEnumerator class with 
            /// the specified sequence to iterate over.
            /// </summary>
            /// <param name="owner">
            /// The sequence to iterate over.
            /// </param>
            public SequenceEnumerator(Sequence owner)
            {
                this.owner = owner;
                this.version = owner.version;
            }

            #endregion

            #endregion

            #region IEnumerator Members

            /// <summary>
            /// Resets the enumerator to just before the first track in the
            /// sequence.
            /// </summary>
            public void Reset()
            {
                // Enforce preconditions.
                if(version != owner.version)
                    throw new InvalidOperationException(
                        "The sequence was modified after the enumerator was created.");

                trackIndex = -1;
            }

            /// <summary>
            /// Moves to the next track in the sequence.
            /// </summary>
            /// <returns>
            /// <b>true</b> if the end of the sequence has not been reached; 
            /// otherwise, <b>false</b>.
            /// </returns>
            public bool MoveNext()
            {
                // Enforce preconditions.
                if(version != owner.version)
                    throw new InvalidOperationException(
                        "The sequence was modified after the enumerator was created.");

                trackIndex++;

                if(trackIndex < owner.Count)
                    return true;
                else
                    return false;
            }

            /// <summary>
            /// The track at the current position.
            /// </summary>
            public object Current
            {
                get
                {
                    // Enforce precondition.
                    if(trackIndex < 0 || trackIndex >= owner.Count)
                        throw new InvalidOperationException(
                            "The enumerator is positioned before the first " +
                            "track of the sequence or after the last track.");

                    return owner[trackIndex];
                }
            }            

            #endregion
        }

        #endregion
    }
}

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 MIT License

Share

About the Author

Leslie Sanford

United States United States
Aside from dabbling in BASIC on his old Atari 1040ST years ago, Leslie's programming experience didn't really begin until he discovered the Internet in the late 90s. There he found a treasure trove of information about two of his favorite interests: MIDI and sound synthesis.
 
After spending a good deal of time calculating formulas he found on the Internet for creating new sounds by hand, he decided that an easier way would be to program the computer to do the work for him. This led him to learn C. He discovered that beyond using programming as a tool for synthesizing sound, he loved programming in and of itself.
 
Eventually he taught himself C++ and C#, and along the way he immersed himself in the ideas of object oriented programming. Like many of us, he gotten bitten by the design patterns bug and a copy of GOF is never far from his hands.
 
Now his primary interest is in creating a complete MIDI toolkit using the C# language. He hopes to create something that will become an indispensable tool for those wanting to write MIDI applications for the .NET framework.
 
Besides programming, his other interests are photography and playing his Les Paul guitars.

| Advertise | Privacy | Mobile
Web03 | 2.8.141015.1 | Last Updated 18 Apr 2007
Article Copyright 2004 by Leslie Sanford
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid