Click here to Skip to main content
15,861,172 members
Articles / Programming Languages / C#

C# MIDI Toolkit

Rate me:
Please Sign up or sign in to vote.
4.95/5 (177 votes)
18 Apr 2007MIT18 min read 3.1M   41.7K   303  
A toolkit for creating MIDI applications with C#.
/*
 * Created by: Leslie Sanford
 * 
 * Contact: jabberdabber@hotmail.com
 * 
 * Last modified: 10/24/2004
 */

using System;
using System.ComponentModel;
using System.Collections;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Multimedia;

namespace Multimedia.Midi
{      
    /// <summary>
    /// Represents Midi input device capabilities.
    /// </summary>
    public struct MidiInCaps
    {
        #region MidiInCaps Members

        /// <summary>
        /// Manufacturer identifier of the device driver for the Midi output 
        /// device. 
        /// </summary>
        public short mid; 

        /// <summary>
        /// Product identifier of the Midi output device. 
        /// </summary>
        public short pid; 

        /// <summary>
        /// Version number of the device driver for the Midi output device. The 
        /// high-order byte is the major version number, and the low-order byte 
        /// is the minor version number. 
        /// </summary>
        public int driverVersion;

        /// <summary>
        /// Product name.
        /// </summary>
        [MarshalAs(UnmanagedType.ByValArray, SizeConst=32)]
        public byte[] name;         

        /// <summary>
        /// Optional functionality supported by the device. 
        /// </summary>
        public int support; 

        #endregion
    }

	/// <summary>
	/// Represents Midi input devices.
	/// </summary>
	public class InputDevice : Component, IMidiDevice, IMidiReceiver
	{
        #region InputDevice Members

        #region Delegates

        // Represents the method that handles messages from Windows.
        private delegate void MidiInProc(int handle, int msg, int instance,
            int param1, int param2); 

        // Represents the method that handles system exclusive headers.
        private delegate void SysExHeaderHandler(IntPtr header);
 
        #endregion

        #region Win32 Midi Input Functions and Constants
 
        [DllImport("winmm.dll")]
        private static extern int midiInOpen(ref int handle, int deviceID,
            MidiInProc proc, int instance, int flags);

        [DllImport("winmm.dll")]
        private static extern int midiInClose(int handle);

        [DllImport("winmm.dll")]
        private static extern int midiInStart(int handle);

        [DllImport("winmm.dll")]
        private static extern int midiInReset(int handle);

        [DllImport("winmm.dll")]
        private static extern int midiInPrepareHeader(int handle, 
            IntPtr header, int sizeOfmidiHeader);

        [DllImport("winmm.dll")]
        private static extern int midiInUnprepareHeader(int handle, 
            IntPtr header, int sizeOfmidiHeader);

        [DllImport("winmm.dll")]
        private static extern int midiInAddBuffer(int handle, 
            IntPtr header, int sizeOfmidiHeader);

        [DllImport("winmm.dll")]
        private static extern int midiInGetDevCaps(int handle, 
            ref MidiInCaps caps, int sizeOfmidiInCaps);

        [DllImport("winmm.dll")]
        private static extern int midiInGetNumDevs();

        [DllImport("winmm.dll")]
        private static extern int midiConnect(int inHandle, int outHandle, 
            int reserved);         

        [DllImport("winmm.dll")]
        private static extern int midiDisconnect(int inHandle, int outHandle, 
            int reserved); 

        private const int MMSYSERR_NOERROR = 0;
        private const int CALLBACK_FUNCTION = 0x30000; 
        private const int MIM_DATA = 0x3C3;
        private const int MIM_ERROR = 0x3C5;
        private const int MIM_LONGDATA = 0x3C4;
        private const int MHDR_DONE = 0x00000001;

        #endregion

        #region Constants

        // Number of system exclusive headers to use.
        private const int HeaderCount = 4;

        // System exclusive buffer size.
        private const int SysExBufferSize = 32000;

        #endregion

        #region Fields

        // Device handle.
        private int handle;

        // Device Identifier.
        private int deviceID;

        // Indicates whether or not the device is open.
        private bool opened = false;

        // Indicates whether or not the device is recording.
        private bool recording = false;        

        // Represents the method that handles messages from Windows.
        private MidiInProc messageHandler; 
        
        // Midi headers for storing system exclusive messages.
        private MidiHeader[] headers = new MidiHeader[HeaderCount];

        // Pointers to headers. 
        private IntPtr[] ptrHeaders = new IntPtr[HeaderCount];        

        // Thread for processing system exclusive headers.
        private Thread sysExHeaderThread;

        // Queue for storing system exclusive headers ready to be processed.
        private Queue sysExHeaderQueue;

        private readonly object lockObject = new object();

		/// <summary>
		/// Required designer variable.
		/// </summary>
		private System.ComponentModel.Container components = null;

        #endregion

        #region Construction

        /// <summary>
        /// Initializes a new instance of the InputDevice class.
        /// </summary>
        public InputDevice()
        {
            //
            // Required for Windows.Forms Class Composition Designer support
            //
            InitializeComponent();

            InitializeInputDevice();
        }

        /// <summary>
        /// Initializes an instance of the InputDevice class with the specified
        /// component container.
        /// </summary>
        /// <param name="container">
        /// The component container to add this instance of the InputDevice 
        /// class.
        /// </param>
		public InputDevice(IContainer container)
		{
			//
			// Required for Windows.Forms Class Composition Designer support
			//
			container.Add(this);
			InitializeComponent();

            InitializeInputDevice();
		}

        /// <summary>
        /// Initializes a new instance of the InputDevice class with the 
        /// specified device Id.
        /// </summary>
        /// <param name="deviceID">
        /// The device Id.
        /// </param>
        public InputDevice(int deviceID)
        {
            //
            // Required for Windows.Forms Class Composition Designer support
            //
            InitializeComponent();

            InitializeInputDevice();

            // Open device.
            Open(deviceID);
        }

        /// <summary>
        /// Initializes an instance of the InputDevice class with the specified
        /// component container and device Id.
        /// </summary>
        /// <param name="container">
        /// The component container to add this instance of the InputDevice 
        /// class.
        /// </param>
        /// <param name="deviceID">
        /// The device Id.
        /// </param>
        public InputDevice(IContainer container, int deviceID)
        {
            //
            // Required for Windows.Forms Class Composition Designer support
            //
            container.Add(this);
            InitializeComponent();

            InitializeInputDevice();

            // Open device.
            Open(deviceID);
        }

        #endregion        

        #region Component Designer generated code
        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }
        #endregion

        #region Methods

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        protected override void Dispose( bool disposing )
        {
            if( disposing )
            {
                if(components != null)
                {
                    components.Dispose();
                }

                if(IsOpen())
                {
                    if(IsRecording())
                    {
                        Stop();
                    }

                    Close();
                }
            }
            base.Dispose( disposing );
        }        

        /// <summary>
        /// Connects to an output device for Midi thru operations.
        /// </summary>
        /// <param name="outDevice">
        /// The output device to connect to.
        /// </param>
        public void Connect(OutputDevice outDevice)
        {
            if(IsOpen() && outDevice.IsOpen())
            {
                midiConnect(handle, outDevice.DeviceHandle, 0);
            }
        }

        /// <summary>
        /// Disconnects from an output device to cease Midi thru operations.
        /// </summary>
        /// <param name="outDevice">
        /// The output device from which to disconnect.
        /// </param>
        public void Disconnect(OutputDevice outDevice)
        {
            if(IsOpen() && outDevice.IsOpen())
            {
                midiDisconnect(handle, outDevice.DeviceHandle, 0);
            }
        }        

        /// <summary>
        /// Gets the input device capabilities.
        /// </summary>
        /// <param name="deviceID">
        /// The device Identifier.
        /// </param>
        /// <exception cref="InputDeviceException">
        /// Thrown if an error occurred while retrieving the input device
        /// capabilities.
        /// </exception>
        /// <returns>
        /// The Midi intput device's capabilities.
        /// </returns>
        public static MidiInCaps GetCapabilities(int deviceID)
        {
            MidiInCaps caps = new MidiInCaps();

            ThrowOnError(midiInGetDevCaps(deviceID, ref caps, 
                Marshal.SizeOf(caps)));

            return caps;
        }

        /// <summary>
        /// Initializes input device.
        /// </summary>
        private void InitializeInputDevice()
        {
            // Create delegate for handling messages from Windows.
            messageHandler = new MidiInProc(OnMessage); 

            // Create queue for holding system exclusive headers ready to be
            // processed.
            sysExHeaderQueue = Queue.Synchronized(new Queue());
        }

        /// <summary>
        /// Handles messages from Windows.
        /// </summary>
        private void OnMessage(int handle, int msg, int instance,
            int param1, int param2)
        { 
            if(IsRecording())
            { 
                if(msg == MIM_DATA)
                {
                    DispatchShortMessage(param1, param2);
                }
                else if(msg == MIM_ERROR)
                {
                    DispatchInvalidShortMsg(param1, param2);
                }
                else if(msg == MIM_LONGDATA)
                {
                    ManageSysExMessage(param1, param2);
                }
            }
        }

        /// <summary>
        /// Throw exception on error.
        /// </summary>
        /// <param name="errCode">
        /// The error code.
        /// </param>
        private static void ThrowOnError(int errCode)
        {
            // If an error occurred.
            if(errCode != MMSYSERR_NOERROR)
            {
                // Throw exception.
                throw new InputDeviceException(errCode);
            }
        }

        /// <summary>
        /// Determines the type of message received and triggers the correct
        /// event in response.
        /// </summary>
        /// <param name="message">
        /// The short Midi message received.
        /// </param>
        /// <param name="timeStamp">
        /// Number of milliseconds that have passed since the input device 
        /// began recording.
        /// </param>
        private void DispatchShortMessage(int message, int timeStamp)
        {
            // Unpack status value.
            int status = ShortMessage.UnpackStatus(message);

            // If a channel message was received.
            if(ChannelMessage.IsChannelMessage(status))
            {
                // If anyone is listening for channel messages.
                if(ChannelMessageReceived != null)
                {
                    // Create channel message.
                    ChannelMessage msg = new ChannelMessage(message);

                    // Create channel message event argument.
                    ChannelMessageEventArgs e = 
                        new ChannelMessageEventArgs(msg, timeStamp);

                    // Trigger channel message received event.
                    ChannelMessageReceived(this, e);
                }
            }
            // Else if a system common message was received
            else if(SysCommonMessage.IsSysCommonMessage(status))
            {
                // If anyone is listening for system common messages
                if(SysCommonReceived != null)
                { 
                    // Create system common message.
                    SysCommonMessage msg = new SysCommonMessage(message);                    
                    
                    // Create system common event argument.
                    SysCommonEventArgs e = new SysCommonEventArgs(msg, timeStamp);

                    // Trigger system common received event.
                    SysCommonReceived(this, e);
                }
            }
            // Else if a system realtime message was received
            else if(SysRealtimeMessage.IsSysRealtimeMessage(status))
            {
                // If anyone is listening for system realtime messages
                if(SysRealtimeReceived != null)
                {
                    // Create system realtime message.
                    SysRealtimeMessage msg = new SysRealtimeMessage(message);

                    // Create system realtime event argument.
                    SysRealtimeEventArgs e = new SysRealtimeEventArgs(msg, timeStamp);

                    // Trigger system realtime received event.
                    SysRealtimeReceived(this, e);
                }
            }
        }

        /// <summary>
        /// Handles triggering the invalid short message received event.
        /// </summary>
        /// <param name="message">
        /// The invalid short message received.
        /// </param>
        /// <param name="timeStamp">
        /// Number of milliseconds that have passed since the input device 
        /// began recording.
        /// </param>
        private void DispatchInvalidShortMsg(int message, int timeStamp)
        {
            if(InvalidShortMessageReceived != null)
            {
                InvalidShortMsgEventArgs e = 
                    new InvalidShortMsgEventArgs(message, timeStamp);

                InvalidShortMessageReceived(this, e);
            }
        }

        /// <summary>
        /// Manages system exclusive messages received by the input device.
        /// </summary>
        /// <param name="param1">
        /// Integer pointer to the header containing the received system
        /// exclusive message.
        /// </param>
        /// <param name="timeStamp">
        /// Number of milliseconds that have passed since the input device 
        /// began recording.
        /// </param>
        private void ManageSysExMessage(int param1, int timeStamp)
        {
            // Get pointer to header.
            IntPtr ptrHeader = new IntPtr(param1);

            // If anyone is listening for system exclusive messages.
            if(SysExReceived != null)
            {
                // Imprint raw pointer on to structure.
                MidiHeader header = (MidiHeader)Marshal.PtrToStructure(ptrHeader, typeof(MidiHeader));
                
                // Dispatches system exclusive messages.
                DispatchSysExMessage(header, timeStamp);
            }

            // Enqueue next system exclusive header and signal the worker queue
            // that another header is ready to be processed.
            lock(lockObject)
            {
                sysExHeaderQueue.Enqueue(ptrHeader);
                Monitor.Pulse(lockObject);
            }
        }

        /// <summary>
        /// Unprepares/prepares and adds a MIDIHDR header back to the buffer to
        /// record another system exclusive message.
        /// </summary>
        private void ManageSysExHeaders()
        {
            lock(lockObject)
            {
                while(IsRecording())
                {
                    Monitor.Wait(lockObject);

                    while(sysExHeaderQueue.Count > 0 && IsRecording())
                    {
                        IntPtr header = (IntPtr)sysExHeaderQueue.Dequeue();

                        // Unprepare header.
                        int result = midiInUnprepareHeader(handle, header, 
                            Marshal.SizeOf(typeof(MidiHeader))); 
            
                        if(result == MMSYSERR_NOERROR)
                        {
                            // Prepare header to be used again.
                            result = midiInPrepareHeader(handle, header, 
                                Marshal.SizeOf(typeof(MidiHeader))); 
                        }

                        if(result == MMSYSERR_NOERROR)
                        { 
                            // Add header back to buffer.
                            result = midiInAddBuffer(handle, header, 
                                Marshal.SizeOf(typeof(MidiHeader)));
                        }

                        if(result != MMSYSERR_NOERROR)
                        {
                            // Raise event letting clients know an error has occurred.
                            if(SysExHeaderErrorOccurred != null)
                            {
                                InputDeviceException ex = new InputDeviceException(result);
                                SysExHeaderErrorOccurred(this, 
                                    new SysExHeaderErrorEventArgs(ex.Message));
                            }
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Handles triggering the system exclusive message received event.
        /// </summary>
        /// <param name="header">
        /// Midi header containing the system exclusive message.
        /// </param>
        /// <param name="timeStamp">
        /// Number of milliseconds that have passed since the input device 
        /// began recording.
        /// </param>
        private void DispatchSysExMessage(MidiHeader header, int timeStamp)
        {
            // Create array for holding system exclusive data.
            byte[] data = new byte[header.bytesRecorded - 1];

            // Get status byte.
            byte status = Marshal.ReadByte(header.data);
                
            // Copy system exclusive data into array (status byte is 
            // excluded).
            for(int i = 1; i < header.bytesRecorded; i++)
            {
                data[i - 1] = Marshal.ReadByte(header.data, i);
            }

            // Create message.
            SysExMessage msg = new SysExMessage((SysExType)status, data);

            // Raise event.
            SysExReceived(this, new SysExEventArgs(msg, timeStamp));
        }

        /// <summary>
        /// Create headers for system exclusive messages.
        /// </summary>
        private void CreateHeaders()
        {
            // Create headers.
            for(int i = 0; i < HeaderCount; i++)
            {
                // Initialize headers and allocate memory for system exclusive
                // data.
                headers[i].bufferLength = SysExBufferSize;
                headers[i].data = Marshal.AllocHGlobal(SysExBufferSize);

                // Allocate memory for pointers to headers. This is necessary 
                // to insure that garbage collection doesn't move the memory 
                // for the headers around while the input device is open.
                ptrHeaders[i] = 
                    Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MidiHeader)));
            }
        }

        /// <summary>
        /// Destroy headers.
        /// </summary>
        private void DestroyHeaders()
        {
            // Free memory for headers.
            for(int i = 0; i < HeaderCount; i++)
            {
                Marshal.FreeHGlobal(headers[i].data);
                Marshal.FreeHGlobal(ptrHeaders[i]);
            }
        }

        /// <summary>
        /// Unprepares headers.
        /// </summary>
        private void UnprepareHeaders()
        {
            // Unprepare each Midi header.
            for(int i = 0; i < HeaderCount; i++)
            {
                ThrowOnError(midiInUnprepareHeader(handle, ptrHeaders[i], 
                    Marshal.SizeOf(typeof(MidiHeader))));
            }
        }        

        #endregion

        #region Properties 
        
        /// <summary>
        /// Gets the number of intput devices present in the system.
        /// </summary>
        public static int DeviceCount
        {
            get
            {
                return midiInGetNumDevs();
            }
        }

        #endregion
       
        #endregion

        #region IMidiDevice Members

        #region Events

        /// <summary>
        /// Occures when the MIDI receiver encounters an error processing 
        /// system exclusive headers.
        /// </summary>
        public event SysExHeaderErrorHandler SysExHeaderErrorOccurred;

        #endregion

        #region Methods

        /// <summary>
        /// Opens the InputDevice with the specified device Identifier.
        /// </summary>
        /// <param name="deviceID">
        /// The device Identifier.
        /// </param>
        /// <exception cref="InputDeviceException">
        /// Thrown if an error occurred while opening the input device.
        /// </exception>
        public void Open(int deviceID)
        {
            // If the device is already open.
            if(IsOpen())
            {
                // Close device before attempting to open it again.
                Close();
            }            

            // Open the device.
            ThrowOnError(midiInOpen(ref handle, deviceID, messageHandler, 0, 
                CALLBACK_FUNCTION));

            // Create headers for system exclusive messages.
            CreateHeaders();            

            // Indicate that the device is open.
            opened = true;

            // Keep track of device Identifier.
            this.deviceID = deviceID;
        }     
      
        /// <summary>
        /// Closes the InputDevice.
        /// </summary>
        /// <exception cref="InputDeviceException">
        /// Thrown if an error occurred while closing the input device.
        /// </exception>
        public void Close()
        {
            // If the device is open.
            if(IsOpen())
            {
                // If the device is recording.
                if(IsRecording())
                {
                    // Stop recording before closing the device.
                    Stop();
                }

                // Destroy headers for system exclusive messages.
                DestroyHeaders();                

                // Close the device.
                ThrowOnError(midiInClose(handle));

                // Indicate that the device is closed.
                opened = false;
            }
        }

        /// <summary>
        /// Indicate whether or not the input device is open
        /// </summary>
        /// <returns>
        /// true if the device is open; otherwise, false.
        /// </returns>
        public bool IsOpen()
        {
            return opened;
        }   

        #endregion

        #region Properties

        /// <summary>
        /// Gets the device handle.
        /// </summary>
        public int DeviceHandle
        {
            get
            {
                return handle;
            }
        }     
   
        /// <summary>
        /// Gets the device ID.
        /// </summary>
        public int DeviceID
        {
            get 
            {
                return deviceID;
            }
        }

        #endregion

        #endregion

        #region IMidiReceiver

        #region Events

        /// <summary>
        /// Occurs when a channel message is received.
        /// </summary>
        public event ChannelMessageEventHandler ChannelMessageReceived;

        /// <summary>
        /// Occurs when a system common message is received.
        /// </summary>
        public event SysCommonEventHandler SysCommonReceived;

        /// <summary>
        /// Occurs when a system exclusive message is received.
        /// </summary>
        public event SysExEventHandler SysExReceived;

        /// <summary>
        /// Occurs when a system realtime message is received.
        /// </summary>
        public event SysRealtimeEventHandler SysRealtimeReceived;

        /// <summary>
        /// Occurs when an invalid short message is received.
        /// </summary>
        public event InvalidShortMessageEventHandler InvalidShortMessageReceived;
        
        #endregion

        #region Methods

        /// <summary>
        /// Starts recording Midi messages.
        /// </summary>
        /// <exception cref="InputDeviceException">
        /// Thrown if there was an error starting the input device.
        /// </exception>
        public void Start()
        {
            // If the device is open and it is not already recording.
            if(IsOpen() && !IsRecording())
            { 
                // Initializes headers for system exclusive messages.
                for(int i = 0; i < HeaderCount; i++)
                { 
                    // Reset flags.
                    headers[i].flags = 0;

                    // Imprint header structure onto raw memory.
                    Marshal.StructureToPtr(headers[i], ptrHeaders[i], false); 

                    // Prepare header.
                    ThrowOnError(midiInPrepareHeader(handle, ptrHeaders[i], 
                        Marshal.SizeOf(typeof(MidiHeader))));

                    // Add header to buffer.
                    ThrowOnError(midiInAddBuffer(handle, ptrHeaders[i], 
                        Marshal.SizeOf(typeof(MidiHeader))));                  
                }

                // Indicate that the device is recording.
                recording = true;

                // Clear system exclusive header queue.
                sysExHeaderQueue.Clear();

                // Create thread for processing system exclusive headers.
                sysExHeaderThread = 
                    new Thread(new ThreadStart(ManageSysExHeaders));

                // Start worker thread.
                sysExHeaderThread.Start();

                // Start recording.
                ThrowOnError(midiInStart(handle));
            }
        }

        /// <summary>
        /// Stop recording Midi messages.
        /// </summary>
        public void Stop()
        {
            // If the device is open.
            if(IsOpen())
            {
                // If the device is recording.
                if(IsRecording())
                {
                    // Indicate that the device is not recording.
                    recording = false;

                    lock(lockObject)
                    {
                        Monitor.Pulse(lockObject);
                    }

                    // Wait for worker thread to finish.
                    sysExHeaderThread.Join();

                    // Stop recording.
                    ThrowOnError(midiInReset(handle));  

                    // Unprepare headers.
                    UnprepareHeaders();
                }
            }
        }

        /// <summary>
        /// Indicates whether or not the device is recording.
        /// </summary>
        /// <returns>
        /// true if the device is recording; otherwise, false.
        /// </returns>
        public bool IsRecording()
        {
            return recording;
        }

        #endregion

        #endregion        
	}

    /// <summary>
    /// The exception that is thrown when a error occurs with the InputDevice
    /// class.
    /// </summary>
    public class InputDeviceException : ApplicationException
    {
        #region InputDeviceException Members

        #region Win32 Midi Input Error Function

        [DllImport("winmm.dll")]
        private static extern int midiInGetErrorText(int errCode, 
            StringBuilder errMsg, int sizeOfErrMsg);

        #endregion

        #region Fields

        // Error message.
        private StringBuilder errMsg = new StringBuilder(128);

        #endregion 

        #region Construction

        /// <summary>
        /// Initializes a new instance of the InputDeviceException class with
        /// the specified error code.
        /// </summary>
        /// <param name="errCode">
        /// The error code.
        /// </param>
        public InputDeviceException(int errCode)
        {
            // Get error message.
            midiInGetErrorText(errCode, errMsg, errMsg.Capacity);
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a message that describes the current exception.
        /// </summary>
        public override string Message
        {
            get
            {
                return errMsg.ToString();
            }
        }

        #endregion

        #endregion
    }

    /// <summary>
    /// Provides data for the InvalidShortMsgEvent event.
    /// </summary>
    public class InvalidShortMsgEventArgs : EventArgs
    {
        #region InvalidShortMsgEventArgs Members

        #region Fields

        private int message;
        private int timeStamp;   
     
        #endregion

        #region Construction

        /// <summary>
        /// Initializes a new instance of the InvalidShortMsgEventArgs class 
        /// with the specified message and time stamp.
        /// </summary>
        /// <param name="message">
        /// The invalid short message as an integer. 
        /// </param>
        /// <param name="timeStamp">
        /// Time in milliseconds since the input device began recording.
        /// </param>
        public InvalidShortMsgEventArgs(int message, int timeStamp)
        {
            this.message = message;
            this.timeStamp = timeStamp;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Invalid short message as an integer.
        /// </summary>
        public int Message
        {
            get
            {
                return message;
            }
        }

        /// <summary>
        /// Time in milliseconds since the input device began recording.
        /// </summary>
        public int TimeStamp
        {
            get
            {
                return timeStamp;
            }
        }

        #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


Written By
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.

Comments and Discussions