Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#

ProjectMIDI: an extensible set of small MIDI .NET programs

Rate me:
Please Sign up or sign in to vote.
4.70/5 (14 votes)
24 Jan 200626 min read 85.1K   3.4K   57  
This article describes how multiple .NET assemblies work together to control MIDI devices in a live performance environment.
using System;
using Microsoft.Win32;			// Needed for RegistryKey
using System.Collections;
using System.Collections.Specialized;
using System.Reflection;
using System.Drawing;
using System.Windows.Forms;

namespace ProjectMidiNS
{
	#region IPMConnectionNotify
	// Used to notify an event or action when a connection has been made to it.
	public interface IPMConnectionNotify
	{
		void RegisterConnection( IPMConnection iconn, bool Connecting );	// Connecting = false when disconnecting.
	}
	#endregion
	#region IPMEventInfo
	public interface IPMEventInfo
	{
		MemberInfo Info { get; }
		string Name { get; set; }
		int NumValues { get; }
		IProjectMidi IProj { get; }
		string Connection { get; }
		int ID { get; }
		ConnectionType ConnType { get; }
		string AsmName { get; }
		string ToolTip { get; }
		void AddEventHandler( object obj, ProjectMidiEventHandler handler );
		void RemoveEventHandler( IProjectMidi iproj, ProjectMidiEventHandler handler );
	}
	#endregion
	#region IPMActionInfo
	public interface IPMActionInfo
	{
		MemberInfo Info { get; }
		string Name { get; }
		string AsmName { get; }
		int NumValues { get; }
		string MethodName { get; }
		string Connection { get; }
		int ID { get; }
		IProjectMidi IProj { get; }
		string	ToolTip { get; }
		void InvokeMethod( object source, EventArgs e );
	}
	#endregion
	#region IPMConnection
	public interface IPMConnection
	{
		string ConnectionName { get; }
		string SourceAsmName { get; set; }
		string SourceName { get; set; }
		IPMEventInfo Source { get; set; }
		string DestAsmName { get; set; }
		string DestName { get; set; }
		IPMActionInfo Dest { get; set; }
		bool Enabled { get; set; }
		ConnectionType ConnType { get; set; }
		int IntData { get; set; }
		string StringData { get; set; }
		bool BoolData { get; set; }
		ProjectMidiEventHandler ActionHandler { get; set; }
		int ID { get; }
	}
	#endregion

	#region Event Arguments
	// EVENT ARGUMENTS
	// These provide polymorphism to eventargs.
	// All event handlers can specify this signature, but receive just about
	// any data type in the 'e' parameter.  
	// The data type can be confirmed and cast using the C# 'as' operator.
	// The IPMConnection pointers are provided to allow 'per-connection' info both directions.
	public delegate void ProjectMidiEventHandler(object sender, EventArgs e);
	public class PMEventArgs : EventArgs
	{
		public IPMConnection iconn;			// Used to pass Connection info to each Action. 
		public IPMConnectionNotify inotify;	// Used to notify an Event's owner of each connection.
	}
	public class IntEventArgs: PMEventArgs 
	{
		public int data;
		public IntEventArgs(int data)
		{
			this.data = data;
		}
	}
	public class StringEventArgs: PMEventArgs 
	{
		public string str;
		public StringEventArgs(string str) 
		{
			this.str = str;
		}
	}
	public class ObjectEventArgs: PMEventArgs 
	{
		public object obj;
		public ObjectEventArgs(object o) 
		{
			obj = o;
		}
	}
	public class MenuEventArgs: PMEventArgs 
	{
		//private int		index;
		private object	obj;
		private string	source;
		public MenuEventArgs(object o, string src) 
		{
			//index = i;
			obj = o;
			source = src;
		}
		//public int Index { get { return index; } }
		public ContextMenu Menu { get { return obj as ContextMenu; } }
		public string Source { get { return source; } }
	}
	#endregion

	#region Attributes
	public enum ConnectionType { Normal, Trace, System }; 
	public enum AssemblyType { Unknown, Device, Applet, Other };
	#region ProjectMidiAssemblyAttribute
	// Assemblies use this to identify themselves to ProjectMIDI.
	// The 'Name' property provides a friendly name to display.
	// The 'Type' property is used to help organize assemblies.
	// Neither is strictly required, but recommended.
	[AttributeUsage(AttributeTargets.Class)]
	public class ProjectMidiAssemblyAttribute : System.Attribute
	{
		private AssemblyType assemblyType;
		private string assemblyName;
		public ProjectMidiAssemblyAttribute()
		{
			assemblyType = AssemblyType.Unknown;
			assemblyName = "unknown";
		}
		public ProjectMidiAssemblyAttribute(AssemblyType type)
		{
			assemblyType = type;
			assemblyName = "unknown";
		}
		public ProjectMidiAssemblyAttribute(AssemblyType type,string name)
		{
			assemblyType = type;
			assemblyName = name;
		}
		public AssemblyType Type
		{
			get { return assemblyType; }
			set { assemblyType = value; }
		}
		public string Name
		{
			get { return assemblyName; }
			set { assemblyName = value; }
		}
	}
	#endregion

	#region ProjectMidiEventAttribute
	// Events must specify this attribute to become visible to ProjectMIDI.
	// 
	[AttributeUsage(AttributeTargets.Event)]
	public class ProjectMidiEventAttribute : System.Attribute
	{
		protected string eventName;
		protected string connection;
		protected ConnectionType connType;
		protected int numValues;
		protected string toolTip;
		public ProjectMidiEventAttribute() : this("",1,"",ConnectionType.Normal,"")
		{
		}
		public ProjectMidiEventAttribute(string name) : this(name,1,"",ConnectionType.Normal,"")
		{
		}
		public ProjectMidiEventAttribute(string name, int numvalues) : this(name,numvalues,"",ConnectionType.Normal,"")
		{
		}
		public ProjectMidiEventAttribute(string name, int numvalues, string connection) 
			: this(name,numvalues,connection,ConnectionType.Normal,"")
		{
		}
		public ProjectMidiEventAttribute(string name, int numvalues, string connection, ConnectionType type) 
			: this(name,numvalues,connection,type,"")
		{
		}
		public ProjectMidiEventAttribute(string name, int numvalues, string connection, ConnectionType type, string tooltip) 
		{
			this.connection = connection;
			eventName = name;
			numValues = numvalues;
			connType = type;
			toolTip = tooltip;
		}
		public string Name
		{
			get { return eventName; }
			set { eventName = value; }
		}
		public int NumValues
		{
			get { return numValues; }
			set { numValues = value; }
		}
		public string Connection
		{
			get { return connection; }
			set { connection = value; }
		}
		public ConnectionType ConnType
		{
			get { return connType; }
			set { connType = value; }
		}
		public string ToolTip
		{
			get { return toolTip; }
			set { toolTip = value; }
		}
	}
	#endregion
	#region ProjectMidiActionAttribute
	[AttributeUsage(AttributeTargets.Method)]
	public class ProjectMidiActionAttribute : System.Attribute
	{
		private string actionName;
		private int numValues;
		private string connection;
		private string toolTip;
		public ProjectMidiActionAttribute() : this("",1,"","")
		{
		}
		public ProjectMidiActionAttribute(string name) : this( name, 1, "", "" )
		{
		}
		public ProjectMidiActionAttribute(string name, int numvalues) : this(name,numvalues,"","")
		{
		}
		public ProjectMidiActionAttribute(string name, int numvalues, string connection)
							: this(name,numvalues,connection,"")
		{
		}
		public ProjectMidiActionAttribute(string name, int numvalues, string connection, string tooltip)
		{
			actionName = name;
			numValues = numvalues;
			this.connection = connection;
			toolTip = tooltip;
		}
		public string Name
		{
			get { return actionName; }
			set { actionName = value; }
		}
		public int NumValues
		{
			get { return numValues; }
			set { numValues = value; }
		}
		public string Connection
		{
			get { return connection; }
			set { connection = value; }
		}
		public string ToolTip
		{
			get { return toolTip; }
			set { toolTip = value; }
		}
	}
	#endregion
	#region ProjectMidiPropertyAttribute
	// These attributes make the property or method public,
	// but provide no usage information. 
	// This allows creating custom API's between assemblies
	// which know about each other.
	[AttributeUsage(AttributeTargets.Property)]
	public class ProjectMidiPropertyAttribute : System.Attribute
	{
		public ProjectMidiPropertyAttribute() {}
	}
	#endregion
	#region ProjectMidiMethodAttribute
	[AttributeUsage(AttributeTargets.Method)]
	public class ProjectMidiMethodAttribute : System.Attribute
	{
		public ProjectMidiMethodAttribute() {}
	}
	#endregion
	#region ProjectMidiMenuEventAttribute
	// Menu Events must specify this attribute to become visible to ProjectMIDI.
	[AttributeUsage(AttributeTargets.Event)]
	public class ProjectMidiMenuEventAttribute : ProjectMidiEventAttribute //System.Attribute
	{
		public ProjectMidiMenuEventAttribute() : base("",1,"",ConnectionType.Normal,"")
		{
		}
		public ProjectMidiMenuEventAttribute(string name) : base(name,1,"",ConnectionType.Normal,"")
		{
		}
		public ProjectMidiMenuEventAttribute(string name, int numvalues) : base(name,numvalues,"",ConnectionType.Normal,"")
		{
		}
		public ProjectMidiMenuEventAttribute(string name, int numvalues,ConnectionType t) : base(name,numvalues,"",t,"")
		{
		}
		public ProjectMidiMenuEventAttribute(string name, int numvalues, string connection,ConnectionType type,string tooltip)
			: base(name,numvalues,connection,type,tooltip)
		{
		}
	}
	#endregion
	#endregion

	#region IHasDisplaySurfaces
	// An assembly implements this interface to enable Ink to write on top of it.
	public interface IHasDisplaySurfaces
	{
		event ProjectMidiEventHandler OnDisplayResize;

		int			NumDisplays { get; }
		Rectangle	GetSize( int index );
		IntPtr		GetHandle( int index );
	}
	#endregion

	//*************
	// INTERFACES
	//*************
	#region IProjectMidi
	// All Project MIDI Assemblies must include a class which derives from this interface.
	// The IProjectMidi interface is used to allow polymorphic access to each assembly.
	// It is the mother of all Project MIDI APIs.
	public interface IProjectMidi
	{

		IProjectMidiParent ProjectMidiParent { set; }
		void PerformStartProcessing( StartPhase phase );
	}

	public enum StartPhase { 
		AfterLoading,				// After assembly is loaded
		AllAssembliesLoaded,		// After all assemblies have been loaded
		StartConnecting,			// Connections are starting to be connected
		FirstTimeEver,				// Only called the first time the assembly is loaded
		AllAssembliesConnected,		// After all assemblies have been loaded and connections made
		InitDone,					// All initialization has completed
		Terminating					// ProjectMIDI is beginning to exit
	}; 

	#endregion
	#region IProjectMidiKeyboard
	// MIDI Keyboards can expose additional functionality by deriving
	// from this interface.  I am planning a keyboard mapping applet
	// which will use this interface.
	// It derives from IProjectMidi, so use this *instead*.
	public interface IProjectMidiKeyboard : IProjectMidi
	{
		int NumZones { get; }

	}
	#endregion
	#region IProjectMidiHideShow
	// This interface is implemented by an assembly if it supports Hide/Show.
	// ProjectMIDI.exe will call this from its icon menu
	// If your assembly displays a dialog, it should support this.
	public interface IProjectMidiHideShow : IProjectMidi
	{
		void HideShow();
	}
	#endregion
	#region IIProjectMidiCollection
	public interface IIProjectMidiCollection : ICollection, IEnumerable
	{
		void Add(IProjectMidi iprojmidi);
		void Remove();
		void Remove(int index);
		IProjectMidi Item(int Index);
	}
	#endregion
	#region IProjectMidiParent
	// This interface provides a back-pointer from the Assembly back to ProjectMidi.exe
	// This pointer is provided as a parameter to the assemblies constructor (see above).
	// The AllAssembliesLoadedEvent is provided because it occurs before Connections have been done.
	public interface IProjectMidiParent : IProjectMidi
	{
		string ProjectMidiPath { get; }					// The directory where all ProjectMIDI folders are placed.
		RegistryKey ProjectMidiKey { get; }				// The registry key for all other ProjectMIDI subkeys.
		IIProjectMidiCollection IProjectMidis { get; }			// Collection of all assemblies IProjectMidi interface
		void CreateAConnection(string eventAsmName, string eventName,
			string actionAsmName,string actionName,
			string connType, string connIntData,
			string connEnabled, string connFollowsSeparator,
			string connStringData);
	}
	#endregion
	#region ISongSet
	public interface ISongSet
	{
		string	CurrentSongName { get; }
		int		CurrentSongNum { get; set; }
		int		CurrentPageNum { get; set; }
		int		CurrentPatchNum { get; set; }

		// These next methods provide persistence of song related parameters.
		// Assemblies can use these to persist per-song data
		string	GetSongParameter( string name, string defaultdata );	// Returns defaultdata if not found.
		void	SetSongParameter( string name, string data );
//		string	GetPageParameter( string name, string defaultdata );	// Returns defaultdata if not found.
//		void	SetPageParameter( string name, string data );
//		string	GetPatchParameter( string name, string defaultdata );	// Returns defaultdata if not found.
//		void	SetPatchParameter( string name, string data );

		ISongCollection Songs { get; }
	}
	#endregion

	#region Song, Page, Patch interfaces
	#region Patch
	// Partial Page Patch
	public interface IPatch
	{
		event ProjectMidiEventHandler OnChanged;
		string	Name { get; set; }
	}
	// Patch collection
	public interface IPatchCollection : ICollection, IEnumerable
	{
		event ProjectMidiEventHandler OnNumPatchesChanged;
		void Add();
		void Add(IPatch patch);
		void Insert(int index, IPatch patch);
		void Remove();
		void Remove(int index);
		IPatch Item(int Index);
	}
	#endregion

	#region Page
	// Page
	public interface IPage
	{
		// TODO: Add Name property
		// TODO: Add OnChanged event
		IPatchCollection	Patches { get; }
	}
	// Page collection
	public interface IPageCollection : ICollection, IEnumerable
	{
		event ProjectMidiEventHandler OnNumPagesChanged;
		void Add(IPage page);
		void Add();
		void Insert(int index, IPage page);
		void Remove();
		void Remove(int index);
		IPage Item(int Index);
	}
	#endregion

	#region Song
	// Song
	public interface ISong
	{
		event ProjectMidiEventHandler OnChanged;
		string			Name { get; set; }
		IPageCollection	Pages { get; }
	}
	// Song collection
	public interface ISongCollection : ICollection, IEnumerable
	{
		event ProjectMidiEventHandler OnSongCollectionChanged;
		bool BatchUpdates { get; set; }
		void Add();
		void Add(ISong song);
		void Insert(int index, ISong song);
		void Remove();
		void Remove(int index);
		ISong Item(int Index);
	}
	#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 has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Software Developer (Senior)
United States United States
Ron is a senior software engineer.
His hobbies include riding motorcycles, travel, and scuba diving.

He enjoys learning about science, particularly quantum physics and cosmology.

He is active with his church where he plays drums and keyboards with the contemporary church band each week.
He also designed and maintains his church and band websites (http://TheRockUMC.org and http://TheRockBand.org).

Comments and Discussions