diff --git a/CpuToYourEars.sln b/CpuToYourEars.sln new file mode 100644 index 0000000..f1f9686 --- /dev/null +++ b/CpuToYourEars.sln @@ -0,0 +1,25 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.26730.12 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CpuToYourEars", "CpuToYourEars\CpuToYourEars.csproj", "{BE9E02BB-0939-4AE7-84F4-EDAA78FE6505}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {BE9E02BB-0939-4AE7-84F4-EDAA78FE6505}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BE9E02BB-0939-4AE7-84F4-EDAA78FE6505}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BE9E02BB-0939-4AE7-84F4-EDAA78FE6505}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BE9E02BB-0939-4AE7-84F4-EDAA78FE6505}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9B0A5D14-0F2C-4F83-B2E1-1B2C0A325FDF} + EndGlobalSection +EndGlobal diff --git a/CpuToYourEars/App.config b/CpuToYourEars/App.config new file mode 100644 index 0000000..662f695 --- /dev/null +++ b/CpuToYourEars/App.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/CpuToYourEars/CommandLineArguments.cs b/CpuToYourEars/CommandLineArguments.cs new file mode 100644 index 0000000..408b4c5 --- /dev/null +++ b/CpuToYourEars/CommandLineArguments.cs @@ -0,0 +1,1559 @@ +////////////////////////////////////////////////////////////////////////////// +// Command Line Argument Parser +// ---------------------------- +// +// Author: peterhal@microsoft.com +// +// Shared Source License for Command Line Parser Library +// +// This license governs use of the accompanying software ('Software'), and your +// use of the Software constitutes acceptance of this license. +// +// You may use the Software for any commercial or noncommercial purpose, +// including distributing derivative works. +// +// In return, we simply require that you agree: +// +// 1. Not to remove any copyright or other notices from the Software. +// 2. That if you distribute the Software in source code form you do so only +// under this license (i.e. you must include a complete copy of this license +// with your distribution), and if you distribute the Software solely in +// object form you only do so under a license that complies with this +// license. +// 3. That the Software comes "as is", with no warranties. None whatsoever. +// This means no express, implied or statutory warranty, including without +// limitation, warranties of merchantability or fitness for a particular +// purpose or any warranty of title or non-infringement. Also, you must pass +// this disclaimer on whenever you distribute the Software or derivative +// works. +// 4. That no contributor to the Software will be liable for any of those types +// of damages known as indirect, special, consequential, or incidental +// related to the Software or this license, to the maximum extent the law +// permits, no matter what legal theory it’s based on. Also, you must pass +// this limitation of liability on whenever you distribute the Software or +// derivative works. +// 5. That if you sue anyone over patents that you think may apply to the +// Software for a person's use of the Software, your license to the Software +// ends automatically. +// 6. That the patent rights, if any, granted in this license only apply to the +// Software, not to any derivative works you make. +// 7. That the Software is subject to U.S. export jurisdiction at the time it +// is licensed to you, and it may be subject to additional export or import +// laws in other places. You agree to comply with all such laws and +// regulations that may apply to the Software after delivery of the software +// to you. +// 8. That if you are an agency of the U.S. Government, (i) Software provided +// pursuant to a solicitation issued on or after December 1, 1995, is +// provided with the commercial license rights set forth in this license, +// and (ii) Software provided pursuant to a solicitation issued prior to +// December 1, 1995, is provided with “Restricted Rights” as set forth in +// FAR, 48 C.F.R. 52.227-14 (June 1987) or DFAR, 48 C.F.R. 252.227-7013 +// (Oct 1988), as applicable. +// 9. That your rights under this License end automatically if you breach it in +// any way. +// 10.That all rights not expressly granted to you in this license are reserved. +// +// Usage +// ----- +// +// Parsing command line arguments to a console application is a common problem. +// This library handles the common task of reading arguments from a command line +// and filling in the values in a type. +// +// To use this library, define a class whose fields represent the data that your +// application wants to receive from arguments on the command line. Then call +// CommandLine.ParseArguments() to fill the object with the data +// from the command line. Each field in the class defines a command line argument. +// The type of the field is used to validate the data read from the command line. +// The name of the field defines the name of the command line option. +// +// The parser can handle fields of the following types: +// +// - string +// - int +// - uint +// - bool +// - enum +// - array of the above type +// +// For example, suppose you want to read in the argument list for wc (word count). +// wc takes three optional boolean arguments: -l, -w, and -c and a list of files. +// +// You could parse these arguments using the following code: +// +// class WCArguments +// { +// public bool lines; +// public bool words; +// public bool chars; +// public string[] files; +// } +// +// class WC +// { +// static void Main(string[] args) +// { +// if (CommandLine.ParseArgumentsWithUsage(args, parsedArgs)) +// { +// // insert application code here +// } +// } +// } +// +// So you could call this aplication with the following command line to count +// lines in the foo and bar files: +// +// wc.exe /lines /files:foo /files:bar +// +// The program will display the following usage message when bad command line +// arguments are used: +// +// wc.exe -x +// +// Unrecognized command line argument '-x' +// /lines[+|-] short form /l +// /words[+|-] short form /w +// /chars[+|-] short form /c +// /files: short form /f +// @ Read response file for more options +// +// That was pretty easy. However, you realy want to omit the "/files:" for the +// list of files. The details of field parsing can be controled using custom +// attributes. The attributes which control parsing behaviour are: +// +// ArgumentAttribute +// - controls short name, long name, required, allow duplicates, default value +// and help text +// DefaultArgumentAttribute +// - allows omition of the "/name". +// - This attribute is allowed on only one field in the argument class. +// +// So for the wc.exe program we want this: +// +// using System; +// using Utilities; +// +// class WCArguments +// { +// [Argument(ArgumentType.AtMostOnce, HelpText="Count number of lines in the input text.")] +// public bool lines; +// [Argument(ArgumentType.AtMostOnce, HelpText="Count number of words in the input text.")] +// public bool words; +// [Argument(ArgumentType.AtMostOnce, HelpText="Count number of chars in the input text.")] +// public bool chars; +// [DefaultArgument(ArgumentType.MultipleUnique, HelpText="Input files to count.")] +// public string[] files; +// } +// +// class WC +// { +// static void Main(string[] args) +// { +// WCArguments parsedArgs = new WCArguments(); +// if (CommandLine.ParseArgumentsWithUsage(args, parsedArgs)) +// { +// // insert application code here +// } +// } +// } +// +// +// +// So now we have the command line we want: +// +// wc.exe /lines foo bar +// +// This will set lines to true and will set files to an array containing the +// strings "foo" and "bar". +// +// The new usage message becomes: +// +// wc.exe -x +// +// Unrecognized command line argument '-x' +// /lines[+|-] Count number of lines in the input text. (short form /l) +// /words[+|-] Count number of words in the input text. (short form /w) +// /chars[+|-] Count number of chars in the input text. (short form /c) +// @ Read response file for more options +// Input files to count. (short form /f) +// +// If you want more control over how error messages are reported, how /help is +// dealt with, etc you can instantiate the CommandLine.Parser class. +// +// +// +// Cheers, +// Peter Hallam +// C# Compiler Developer +// Microsoft Corp. +// +// +// +// +// Release Notes +// ------------- +// +// 10/02/2002 Initial Release +// 10/14/2002 Bug Fix +// 01/08/2003 Bug Fix in @ include files +// 10/23/2004 Added user specified help text, formatting of help text to +// screen width. Added ParseHelp for /?. +// 11/23/2004 Added support for default values. +// 02/23/2005 Fix bug with short name and default arguments. +// 03/12/2007 Added support for non case sensitive option names. +////////////////////////////////////////////////////////////////////////////// + +namespace Microsoft.CommandLineHelper +{ + using System; + using System.Diagnostics; + using System.Reflection; + using System.Collections; + using System.IO; + using System.Text; + using System.Runtime.InteropServices; + + /// + /// Used to control parsing of command line arguments. + /// + [Flags] + public enum ArgumentType + { + /// + /// Indicates that this field is required. An error will be displayed + /// if it is not present when parsing arguments. + /// + Required = 0x01, + /// + /// Only valid in conjunction with Multiple. + /// Duplicate values will result in an error. + /// + Unique = 0x02, + /// + /// Inidicates that the argument may be specified more than once. + /// Only valid if the argument is a collection + /// + Multiple = 0x04, + + /// + /// The default type for non-collection arguments. + /// The argument is not required, but an error will be reported if it is specified more than once. + /// + AtMostOnce = 0x00, + + /// + /// For non-collection arguments, when the argument is specified more than + /// once no error is reported and the value of the argument is the last + /// value which occurs in the argument list. + /// + LastOccurenceWins = Multiple, + + /// + /// The default type for collection arguments. + /// The argument is permitted to occur multiple times, but duplicate + /// values will cause an error to be reported. + /// + MultipleUnique = Multiple | Unique, + } + + /// + /// Allows control of command line parsing. + /// Attach this attribute to instance fields of types used + /// as the destination of command line argument parsing. + /// + [AttributeUsage(AttributeTargets.Field)] + public class ArgumentAttribute : Attribute + { + /// + /// Allows control of command line parsing. + /// + /// Specifies the error checking to be done on the argument. + public ArgumentAttribute(ArgumentType type) + { + this.type = type; + } + + /// + /// The error checking to be done on the argument. + /// + public ArgumentType Type + { + get { return this.type; } + } + /// + /// Returns true if the argument did not have an explicit short name specified. + /// + public bool DefaultShortName { get { return null == this.shortName; } } + + /// + /// The short name of the argument. + /// Set to null means use the default short name if it does not + /// conflict with any other parameter name. + /// Set to String.Empty for no short name. + /// This property should not be set for DefaultArgumentAttributes. + /// + public string ShortName + { + get { return this.shortName; } + set { Debug.Assert(value == null || !(this is DefaultArgumentAttribute)); this.shortName = value; } + } + + /// + /// Returns true if the argument did not have an explicit long name specified. + /// + public bool DefaultLongName { get { return null == this.longName; } } + + /// + /// The long name of the argument. + /// Set to null means use the default long name. + /// The long name for every argument must be unique. + /// It is an error to specify a long name of String.Empty. + /// + public string LongName + { + get { Debug.Assert(!this.DefaultLongName); return this.longName; } + set { Debug.Assert(value != ""); this.longName = value; } + } + + /// + /// The default value of the argument. + /// + public object DefaultValue + { + get { return this.defaultValue; } + set { this.defaultValue = value; } + } + + /// + /// Returns true if the argument has a default value. + /// + public bool HasDefaultValue { get { return null != this.defaultValue; } } + + /// + /// Returns true if the argument has help text specified. + /// + public bool HasHelpText { get { return null != this.helpText; } } + + /// + /// The help text for the argument. + /// + public string HelpText + { + get { return this.helpText; } + set { this.helpText = value; } + } + + private string shortName; + private string longName; + private string helpText; + private object defaultValue; + private ArgumentType type; + } + + /// + /// Indicates that this argument is the default argument. + /// '/' or '-' prefix only the argument value is specified. + /// The ShortName property should not be set for DefaultArgumentAttribute + /// instances. The LongName property is used for usage text only and + /// does not affect the usage of the argument. + /// + [AttributeUsage(AttributeTargets.Field)] + public class DefaultArgumentAttribute : ArgumentAttribute + { + /// + /// Indicates that this argument is the default argument. + /// + /// Specifies the error checking to be done on the argument. + public DefaultArgumentAttribute(ArgumentType type) + : base (type) + { + } + } + + /// + /// Indicates that a type represents command line arguments. + /// adding this attribute is optional, and can be sued to override + /// default behavior for case sensitivity. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct)] + public class CommandLineArgumentsAttribute : Attribute + { + public CommandLineArgumentsAttribute() { } + + /// + /// Should argument name parsing respect case + /// + public bool CaseSensitive + { + get { return this.caseSensitive; } + set { this.caseSensitive = value; } + } + + private bool caseSensitive = true; + } + + /// + /// A delegate used in error reporting. + /// + public delegate void ErrorReporter(string message); + + /// + /// Parser for command line arguments. + /// + /// The parser specification is infered from the instance fields of the object + /// specified as the destination of the parse. + /// Valid argument types are: int, uint, string, bool, enums + /// Also argument types of Array of the above types are also valid. + /// + /// Error checking options can be controlled by adding a ArgumentAttribute + /// to the instance fields of the destination object. + /// + /// At most one field may be marked with the DefaultArgumentAttribute + /// indicating that arguments without a '-' or '/' prefix will be parsed as that argument. + /// + /// If not specified then the parser will infer default options for parsing each + /// instance field. The default long name of the argument is the field name. The + /// default short name is the first character of the long name. Long names and explicitly + /// specified short names must be unique. Default short names will be used provided that + /// the default short name does not conflict with a long name or an explicitly + /// specified short name. + /// + /// Arguments which are array types are collection arguments. Collection + /// arguments can be specified multiple times. + /// + public sealed class Parser + { + /// + /// The System Defined new line string. + /// + public const string NewLine = "\r\n"; + + /// + /// Don't ever call this. + /// + private Parser() { } + + /// + /// Parses Command Line Arguments. Displays usage message to Console.Out + /// if /?, /help or invalid arguments are encounterd. + /// Errors are output on Console.Error. + /// Use ArgumentAttributes to control parsing behaviour. + /// + /// The actual arguments. + /// The resulting parsed arguments. + /// true if no errors were detected. + public static bool ParseArgumentsWithUsage(string [] arguments, object destination) + { + if (Parser.ParseHelp(arguments) || !Parser.ParseArguments(arguments, destination)) + { + // error encountered in arguments. Display usage message + System.Console.Write(Parser.ArgumentsUsage(destination.GetType())); + return false; + } + + return true; + } + + /// + /// Parses Command Line Arguments. + /// Errors are output on Console.Error. + /// Use ArgumentAttributes to control parsing behaviour. + /// + /// The actual arguments. + /// The resulting parsed arguments. + /// true if no errors were detected. + public static bool ParseArguments(string [] arguments, object destination) + { + return Parser.ParseArguments(arguments, destination, new ErrorReporter(Console.Error.WriteLine)); + } + + /// + /// Parses Command Line Arguments. + /// Use ArgumentAttributes to control parsing behaviour. + /// + /// The actual arguments. + /// The resulting parsed arguments. + /// The destination for parse errors. + /// true if no errors were detected. + public static bool ParseArguments(string[] arguments, object destination, ErrorReporter reporter) + { + Parser parser = new Parser(destination.GetType(), reporter); + return parser.Parse(arguments, destination); + } + + private static void NullErrorReporter(string message) + { + } + + private class HelpArgument + { + [ArgumentAttribute(ArgumentType.AtMostOnce, ShortName="?")] + public bool help = false; + } + + /// + /// Checks if a set of arguments asks for help. + /// + /// Args to check for help. + /// Returns true if args contains /? or /help. + public static bool ParseHelp(string[] args) + { + Parser helpParser = new Parser(typeof(HelpArgument), new ErrorReporter(NullErrorReporter)); + HelpArgument helpArgument = new HelpArgument(); + helpParser.Parse(args, helpArgument); + return helpArgument.help; + } + + + /// + /// Returns a Usage string for command line argument parsing. + /// Use ArgumentAttributes to control parsing behaviour. + /// Formats the output to the width of the current console window. + /// + /// The type of the arguments to display usage for. + /// Printable string containing a user friendly description of command line arguments. + public static string ArgumentsUsage(Type argumentType) + { + int screenWidth = Parser.GetConsoleWindowWidth(); + if (screenWidth == 0) + screenWidth = 80; + return ArgumentsUsage(argumentType, screenWidth); + } + + /// + /// Returns a Usage string for command line argument parsing. + /// Use ArgumentAttributes to control parsing behaviour. + /// + /// The type of the arguments to display usage for. + /// The number of columns to format the output to. + /// Printable string containing a user friendly description of command line arguments. + public static string ArgumentsUsage(Type argumentType, int columns) + { + return (new Parser(argumentType, null)).GetUsageString(columns); + } + + private const int STD_OUTPUT_HANDLE = -11; + + private struct COORD + { + internal Int16 x; + internal Int16 y; + } + + private struct SMALL_RECT + { + internal Int16 Left; + internal Int16 Top; + internal Int16 Right; + internal Int16 Bottom; + } + + private struct CONSOLE_SCREEN_BUFFER_INFO + { + internal COORD dwSize; + internal COORD dwCursorPosition; + internal Int16 wAttributes; + internal SMALL_RECT srWindow; + internal COORD dwMaximumWindowSize; + } + + [DllImport("kernel32.dll", EntryPoint="GetStdHandle", SetLastError=true, CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)] + private static extern int GetStdHandle(int nStdHandle); + + [DllImport("kernel32.dll", EntryPoint="GetConsoleScreenBufferInfo", SetLastError=true, CharSet=CharSet.Auto, CallingConvention=CallingConvention.StdCall)] + private static extern int GetConsoleScreenBufferInfo(int hConsoleOutput, ref CONSOLE_SCREEN_BUFFER_INFO lpConsoleScreenBufferInfo); + + /// + /// Returns the number of columns in the current console window + /// + /// Returns the number of columns in the current console window + public static int GetConsoleWindowWidth() + { + int screenWidth; + CONSOLE_SCREEN_BUFFER_INFO csbi = new CONSOLE_SCREEN_BUFFER_INFO(); + + int rc; + rc = GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), ref csbi); + screenWidth = csbi.dwSize.x; + return screenWidth; + } + + /// + /// Searches a StringBuilder for a character + /// + /// The text to search. + /// The character value to search for. + /// The index to stat searching at. + /// The index of the first occurence of value or -1 if it is not found. + public static int IndexOf(StringBuilder text, char value, int startIndex) + { + for (int index = startIndex; index < text.Length; index++) + { + if (text[index] == value) + return index; + } + + return -1; + } + + /// + /// Searches a StringBuilder for a character in reverse + /// + /// The text to search. + /// The character to search for. + /// The index to start the search at. + /// The index of the last occurence of value in text or -1 if it is not found. + public static int LastIndexOf(StringBuilder text, char value, int startIndex) + { + for (int index = Math.Min(startIndex, text.Length - 1); index >= 0; index --) + { + if (text[index] == value) + return index; + } + + return -1; + } + + private const int spaceBeforeParam = 2; + + /// + /// Creates a new command line argument parser. + /// + /// The type of object to parse. + /// The destination for parse errors. + public Parser(Type argumentSpecification, ErrorReporter reporter) + { + this.reporter = reporter; + this.arguments = new ArrayList(); + CommandLineArgumentsAttribute typeAttribute = GetAttribute(argumentSpecification); + bool caseSensitive = (typeAttribute == null || typeAttribute.CaseSensitive); + this.argumentMap = new Hashtable(caseSensitive ? StringComparer.Ordinal : StringComparer.InvariantCultureIgnoreCase); + + foreach (FieldInfo field in argumentSpecification.GetFields()) + { + if (!field.IsStatic && !field.IsInitOnly && !field.IsLiteral) + { + ArgumentAttribute attribute = GetAttribute(field); + if (attribute is DefaultArgumentAttribute) + { + Debug.Assert(this.defaultArgument == null); + this.defaultArgument = new Argument(attribute, field, reporter); + } + else + { + this.arguments.Add(new Argument(attribute, field, reporter)); + } + } + } + + AddExplicitArgumentNames(); + AddImplicitArgumentNames(); + } + + private static ArgumentAttribute GetAttribute(FieldInfo field) + { + return (ArgumentAttribute)GetAttribute(field, typeof(ArgumentAttribute)); + } + + private static CommandLineArgumentsAttribute GetAttribute(Type type) + { + return (CommandLineArgumentsAttribute)GetAttribute(type, typeof(CommandLineArgumentsAttribute)); + } + + private static object GetAttribute(MemberInfo member, Type attributeType) + { + object[] attributes = member.GetCustomAttributes(attributeType, false); + if (attributes.Length == 1) + return attributes[0]; + + Debug.Assert(attributes.Length == 0); + return null; + } + + private void AddExplicitArgumentNames() + { + foreach (Argument argument in this.arguments) + { + Debug.Assert(!this.argumentMap.ContainsKey(argument.LongName)); + + this.argumentMap[argument.LongName] = argument; + if (argument.ExplicitShortName) + { + if (argument.HasShortName) + { + Debug.Assert(!this.argumentMap.ContainsKey(argument.ShortName)); + + this.argumentMap[argument.ShortName] = argument; + } + else + { + argument.ClearShortName(); + } + } + } + } + + private void AddImplicitArgumentNames() + { + foreach (Argument argument in this.arguments) + { + if (!argument.ExplicitShortName) + { + if (argument.HasShortName && !this.argumentMap.ContainsKey(argument.ShortName)) + { + this.argumentMap[argument.ShortName] = argument; + } + else + { + argument.ClearShortName(); + } + } + } + } + + private void ReportUnrecognizedArgument(string argument) + { + this.reporter(string.Format("Unrecognized command line argument '{0}'", argument)); + } + + /// + /// Parses an argument list into an object + /// + /// + /// + /// true if an error occurred + private bool ParseArgumentList(string[] args, object destination) + { + bool hadError = false; + if (args != null) + { + foreach (string argument in args) + { + if (argument.Length > 0) + { + switch (argument[0]) + { + case '-': + case '/': + string option; + string optionArgument; + GetOptionNameAndArgument(argument, out option, out optionArgument); + + // check for both case sensitive and case insensitive options + Argument arg = ArgumentFromString(option); + + if (arg == null) + { + ReportUnrecognizedArgument(argument); + hadError = true; + } + else + { + hadError |= !arg.SetValue(optionArgument, destination); + } + break; + case '@': + string[] nestedArguments; + hadError |= LexFileArguments(argument.Substring(1), out nestedArguments); + hadError |= ParseArgumentList(nestedArguments, destination); + break; + default: + if (this.defaultArgument != null) + { + hadError |= !this.defaultArgument.SetValue(argument, destination); + } + else + { + ReportUnrecognizedArgument(argument); + hadError = true; + } + break; + } + } + } + } + + return hadError; + } + + private static void GetOptionNameAndArgument(string argument, out string option, out string optionArgument) + { + int endIndex = argument.IndexOfAny(new char[] { ':', '+', '-' }, 1); + option = argument.Substring(1, endIndex == -1 ? argument.Length - 1 : endIndex - 1); + + if (option.Length + 1 == argument.Length) + { + optionArgument = null; + } + else if (argument.Length > 1 + option.Length && argument[1 + option.Length] == ':') + { + optionArgument = argument.Substring(option.Length + 2); + } + else + { + optionArgument = argument.Substring(option.Length + 1); + } + } + + private Argument ArgumentFromString(string option) + { + return (Argument)this.argumentMap[option]; + } + + /// + /// Parses an argument list. + /// + /// The arguments to parse. + /// The destination of the parsed arguments. + /// true if no parse errors were encountered. + public bool Parse(string[] args, object destination) + { + bool hadError = ParseArgumentList(args, destination); + + // check for missing required arguments + foreach (Argument arg in this.arguments) + { + hadError |= arg.Finish(destination); + } + if (this.defaultArgument != null) + { + hadError |= this.defaultArgument.Finish(destination); + } + + return !hadError; + } + + private struct ArgumentHelpStrings + { + public ArgumentHelpStrings(string syntax, string help) + { + this.syntax = syntax; + this.help = help; + } + + public string syntax; + public string help; + } + + /// + /// A user firendly usage string describing the command line argument syntax. + /// + public string GetUsageString(int screenWidth) + { + ArgumentHelpStrings[] strings = GetAllHelpStrings(); + + int maxParamLen = 0; + foreach (ArgumentHelpStrings helpString in strings) + { + maxParamLen = Math.Max(maxParamLen, helpString.syntax.Length); + } + + const int minimumNumberOfCharsForHelpText = 10; + const int minimumHelpTextColumn = 5; + const int minimumScreenWidth = minimumHelpTextColumn + minimumNumberOfCharsForHelpText; + + int helpTextColumn; + int idealMinimumHelpTextColumn = maxParamLen + spaceBeforeParam; + screenWidth = Math.Max(screenWidth, minimumScreenWidth); + if (screenWidth < (idealMinimumHelpTextColumn + minimumNumberOfCharsForHelpText)) + helpTextColumn = minimumHelpTextColumn; + else + helpTextColumn = idealMinimumHelpTextColumn; + + const string newLine = "\n"; + StringBuilder builder = new StringBuilder(); + foreach (ArgumentHelpStrings helpStrings in strings) + { + // add syntax string + int syntaxLength = helpStrings.syntax.Length; + builder.Append(helpStrings.syntax); + + // start help text on new line if syntax string is too long + int currentColumn = syntaxLength; + if (syntaxLength >= helpTextColumn) + { + builder.Append(newLine); + currentColumn = 0; + } + + // add help text broken on spaces + int charsPerLine = screenWidth - helpTextColumn; + int index = 0; + while (index < helpStrings.help.Length) + { + // tab to start column + builder.Append(' ', helpTextColumn - currentColumn); + currentColumn = helpTextColumn; + + // find number of chars to display on this line + int endIndex = index + charsPerLine; + if (endIndex >= helpStrings.help.Length) + { + // rest of text fits on this line + endIndex = helpStrings.help.Length; + } + else + { + endIndex = helpStrings.help.LastIndexOf(' ', endIndex - 1, Math.Min(endIndex - index, charsPerLine)); + if (endIndex <= index) + { + // no spaces on this line, append full set of chars + endIndex = index + charsPerLine; + } + } + + // add chars + builder.Append(helpStrings.help, index, endIndex - index); + index = endIndex; + + // do new line + AddNewLine(newLine, builder, ref currentColumn); + + // don't start a new line with spaces + while (index < helpStrings.help.Length && helpStrings.help[index] == ' ') + index ++; + } + + // add newline if there's no help text + if (helpStrings.help.Length == 0) + { + builder.Append(newLine); + } + } + + return builder.ToString(); + } + private static void AddNewLine(string newLine, StringBuilder builder, ref int currentColumn) + { + builder.Append(newLine); + currentColumn = 0; + } + private ArgumentHelpStrings[] GetAllHelpStrings() + { + ArgumentHelpStrings[] strings = new ArgumentHelpStrings[NumberOfParametersToDisplay()]; + + int index = 0; + foreach (Argument arg in this.arguments) + { + strings[index] = GetHelpStrings(arg); + index++; + } + + // Commenting out line below because we don't want to show @ argument + //strings[index++] = new ArgumentHelpStrings("@", "Read response file for more options"); + + if (this.defaultArgument != null) + strings[index++] = GetHelpStrings(this.defaultArgument); + + return strings; + } + + private static ArgumentHelpStrings GetHelpStrings(Argument arg) + { + return new ArgumentHelpStrings(arg.SyntaxHelp, arg.FullHelpText); + } + + private int NumberOfParametersToDisplay() + { + int numberOfParameters = this.arguments.Count; + + // Commenting out line below because we don't want to show @ argument + //numberOfParameters++; + + if (HasDefaultArgument) + numberOfParameters += 1; + return numberOfParameters; + } + + /// + /// Does this parser have a default argument. + /// + /// Does this parser have a default argument. + public bool HasDefaultArgument + { + get { return this.defaultArgument != null; } + } + + private bool LexFileArguments(string fileName, out string[] arguments) + { + string args = null; + + try + { + using (FileStream file = new FileStream(fileName, FileMode.Open, FileAccess.Read)) + { + args = (new StreamReader(file)).ReadToEnd(); + } + } + catch (Exception e) + { + this.reporter(string.Format("Error: Can't open command line argument file '{0}' : '{1}'", fileName, e.Message)); + arguments = null; + return false; + } + + bool hadError = false; + ArrayList argArray = new ArrayList(); + StringBuilder currentArg = new StringBuilder(); + bool inQuotes = false; + int index = 0; + + // while (index < args.Length) + try + { + while (true) + { + // skip whitespace + while (char.IsWhiteSpace(args[index])) + { + index += 1; + } + + // # - comment to end of line + if (args[index] == '#') + { + index += 1; + while (args[index] != '\n') + { + index += 1; + } + continue; + } + + // do one argument + do + { + if (args[index] == '\\') + { + int cSlashes = 1; + index += 1; + while (index == args.Length && args[index] == '\\') + { + cSlashes += 1; + } + + if (index == args.Length || args[index] != '"') + { + currentArg.Append('\\', cSlashes); + } + else + { + currentArg.Append('\\', (cSlashes >> 1)); + if (0 != (cSlashes & 1)) + { + currentArg.Append('"'); + } + else + { + inQuotes = !inQuotes; + } + } + } + else if (args[index] == '"') + { + inQuotes = !inQuotes; + index += 1; + } + else + { + currentArg.Append(args[index]); + index += 1; + } + } while (!char.IsWhiteSpace(args[index]) || inQuotes); + argArray.Add(currentArg.ToString()); + currentArg.Length = 0; + } + } + catch (System.IndexOutOfRangeException) + { + // got EOF + if (inQuotes) + { + this.reporter(string.Format("Error: Unbalanced '\"' in command line argument file '{0}'", fileName)); + hadError = true; + } + else if (currentArg.Length > 0) + { + // valid argument can be terminated by EOF + argArray.Add(currentArg.ToString()); + } + } + + arguments = (string[]) argArray.ToArray(typeof (string)); + return hadError; + } + + private static string LongName(ArgumentAttribute attribute, FieldInfo field) + { + return (attribute == null || attribute.DefaultLongName) ? field.Name : attribute.LongName; + } + + private static string ShortName(ArgumentAttribute attribute, FieldInfo field) + { + if (attribute is DefaultArgumentAttribute) + return null; + if (!ExplicitShortName(attribute)) + return LongName(attribute, field).Substring(0,1); + return attribute.ShortName; + } + + private static string HelpText(ArgumentAttribute attribute, FieldInfo field) + { + if (attribute == null) + return null; + else + return attribute.HelpText; + } + + private static bool HasHelpText(ArgumentAttribute attribute) + { + return (attribute != null && attribute.HasHelpText); + } + + private static bool ExplicitShortName(ArgumentAttribute attribute) + { + return (attribute != null && !attribute.DefaultShortName); + } + + private static object DefaultValue(ArgumentAttribute attribute, FieldInfo field) + { + return (attribute == null || !attribute.HasDefaultValue) ? null : attribute.DefaultValue; + } + + private static Type ElementType(FieldInfo field) + { + if (IsCollectionType(field.FieldType)) + return field.FieldType.GetElementType(); + else + return null; + } + + private static ArgumentType Flags(ArgumentAttribute attribute, FieldInfo field) + { + if (attribute != null) + return attribute.Type; + else if (IsCollectionType(field.FieldType)) + return ArgumentType.MultipleUnique; + else + return ArgumentType.AtMostOnce; + } + + private static bool IsCollectionType(Type type) + { + return type.IsArray; + } + + private static bool IsValidElementType(Type type) + { + return type != null && ( + type == typeof(int) || + type == typeof(uint) || + type == typeof(string) || + type == typeof(bool) || + type.IsEnum); + } + + [System.Diagnostics.DebuggerDisplay("Name = {LongName}")] + private class Argument + { + public Argument(ArgumentAttribute attribute, FieldInfo field, ErrorReporter reporter) + { + this.longName = Parser.LongName(attribute, field); + this.explicitShortName = Parser.ExplicitShortName(attribute); + this.shortName = Parser.ShortName(attribute, field); + this.hasHelpText = Parser.HasHelpText(attribute); + this.helpText = Parser.HelpText(attribute, field); + this.defaultValue = Parser.DefaultValue(attribute, field); + this.elementType = ElementType(field); + this.flags = Flags(attribute, field); + this.field = field; + this.seenValue = false; + this.reporter = reporter; + this.isDefault = attribute != null && attribute is DefaultArgumentAttribute; + + if (IsCollection) + { + this.collectionValues = new ArrayList(); + } + + Debug.Assert(this.longName != null && this.longName != ""); + Debug.Assert(!this.isDefault || !this.ExplicitShortName); + Debug.Assert(!IsCollection || AllowMultiple, "Collection arguments must have allow multiple"); + Debug.Assert(!Unique || IsCollection, "Unique only applicable to collection arguments"); + Debug.Assert(IsValidElementType(Type) || + IsCollectionType(Type)); + Debug.Assert((IsCollection && IsValidElementType(elementType)) || + (!IsCollection && elementType == null)); + Debug.Assert(!(this.IsRequired && this.HasDefaultValue), "Required arguments cannot have default value"); + Debug.Assert(!this.HasDefaultValue || (this.defaultValue.GetType() == field.FieldType), "Type of default value must match field type"); + } + + public bool Finish(object destination) + { + if (this.SeenValue) + { + if (this.IsCollection) + { + this.field.SetValue(destination, this.collectionValues.ToArray(this.elementType)); + } + } + else + { + if (this.HasDefaultValue) + { + this.field.SetValue(destination, this.DefaultValue); + } + } + + return ReportMissingRequiredArgument(); + } + + private bool ReportMissingRequiredArgument() + { + if (this.IsRequired && !this.SeenValue) + { + if (this.IsDefault) + reporter(string.Format("Missing required argument '<{0}>'.", this.LongName)); + else + reporter(string.Format("Missing required argument '/{0}'.", this.LongName)); + return true; + } + return false; + } + + private void ReportDuplicateArgumentValue(string value) + { + this.reporter(string.Format("Duplicate '{0}' argument '{1}'", this.LongName, value)); + } + + public bool SetValue(string value, object destination) + { + if (SeenValue && !AllowMultiple) + { + this.reporter(string.Format("Duplicate '{0}' argument", this.LongName)); + return false; + } + this.seenValue = true; + + object newValue; + if (!ParseValue(this.ValueType, value, out newValue)) + return false; + if (this.IsCollection) + { + if (this.Unique && this.collectionValues.Contains(newValue)) + { + ReportDuplicateArgumentValue(value); + return false; + } + else + { + this.collectionValues.Add(newValue); + } + } + else + { + this.field.SetValue(destination, newValue); + } + + return true; + } + + public Type ValueType + { + get { return this.IsCollection ? this.elementType : this.Type; } + } + + private void ReportBadArgumentValue(string value) + { + this.reporter(string.Format("'{0}' is not a valid value for the '{1}' command line option", value, this.LongName)); + } + + private bool ParseValue(Type type, string stringData, out object value) + { + // null is only valid for bool variables + // empty string is never valid + if ((stringData != null || type == typeof(bool)) && (stringData == null || stringData.Length > 0)) + { + try + { + if (type == typeof(string)) + { + value = stringData; + return true; + } + else if (type == typeof(bool)) + { + if (stringData == null || stringData == "+") + { + value = true; + return true; + } + else if (stringData == "-") + { + value = false; + return true; + } + } + else if (type == typeof(int)) + { + value = int.Parse(stringData); + return true; + } + else if (type == typeof(uint)) + { + value = int.Parse(stringData); + return true; + } + else + { + Debug.Assert(type.IsEnum); + + bool valid = false; + foreach (string name in Enum.GetNames(type)) + { + if (name == stringData) + { + valid = true; + break; + } + } + if (valid) + { + value = Enum.Parse(type, stringData, true); + return true; + } + } + } + catch + { + // catch parse errors + } + } + + ReportBadArgumentValue(stringData); + value = null; + return false; + } + + private void AppendValue(StringBuilder builder, object value) + { + if (value is string || value is int || value is uint || value.GetType().IsEnum) + { + builder.Append(value.ToString()); + } + else if (value is bool) + { + builder.Append((bool) value ? "+" : "-"); + } + else + { + bool first = true; + foreach (object o in (System.Array) value) + { + if (!first) + { + builder.Append(", "); + } + AppendValue(builder, o); + first = false; + } + } + } + + public string LongName + { + get { return this.longName; } + } + + public bool ExplicitShortName + { + get { return this.explicitShortName; } + } + + public string ShortName + { + get + { + Debug.Assert(this.HasShortName); + return this.shortName; + } + } + + public bool HasShortName + { + get { return this.shortName != null && this.shortName.Length > 0; } + } + + public void ClearShortName() + { + this.shortName = null; + } + + public bool HasHelpText + { + get { return this.hasHelpText; } + } + + public string HelpText + { + get { return this.helpText; } + } + + public object DefaultValue + { + get { return this.defaultValue; } + } + + public bool HasDefaultValue + { + get { return null != this.defaultValue; } + } + + public string FullHelpText + { + get { + StringBuilder builder = new StringBuilder(); + if (this.HasHelpText) + { + builder.Append(this.HelpText); + } + if (this.HasDefaultValue) + { + if (builder.Length > 0) + builder.Append(" "); + builder.Append("Default value:'"); + AppendValue(builder, this.DefaultValue); + builder.Append('\''); + } + if (this.HasShortName) + { + if (builder.Length > 0) + builder.Append(" "); + builder.Append("Short form is /"); + builder.Append(this.ShortName); + builder.Append(":"); + builder.Append(this.ValueTypeString); + builder.Append("."); + } + return builder.ToString(); + } + } + + public string SyntaxHelp + { + get + { + StringBuilder builder = new StringBuilder(); + + if (this.IsDefault) + { + builder.Append("<"); + builder.Append(this.LongName); + builder.Append(">"); + } + else + { + builder.Append("/"); + builder.Append(this.LongName); + builder.Append(":"); + builder.Append(ValueTypeString); + } + + return builder.ToString(); + } + } + + public string ValueTypeString + { + get + { + Type valueType = this.ValueType; + string type; + if (valueType == typeof(int)) + type = ""; + else if (valueType == typeof(uint)) + type = ""; + else if (valueType == typeof(bool)) + type = "[+|-]"; + else if (valueType == typeof(string)) + type = ""; + else + { + Debug.Assert(valueType.IsEnum); + var builder = new StringBuilder(); + builder.Append("{"); + bool first = true; + foreach (FieldInfo field in valueType.GetFields()) + { + if (field.IsStatic) + { + if (first) + first = false; + else + builder.Append('|'); + builder.Append(field.Name); + } + } + builder.Append('}'); + type = builder.ToString(); + } + return type; + } + } + + public bool IsRequired + { + get { return 0 != (this.flags & ArgumentType.Required); } + } + + public bool SeenValue + { + get { return this.seenValue; } + } + + public bool AllowMultiple + { + get { return 0 != (this.flags & ArgumentType.Multiple); } + } + + public bool Unique + { + get { return 0 != (this.flags & ArgumentType.Unique); } + } + + public Type Type + { + get { return field.FieldType; } + } + + public bool IsCollection + { + get { return IsCollectionType(Type); } + } + + public bool IsDefault + { + get { return this.isDefault; } + } + + private string longName; + private string shortName; + private string helpText; + private bool hasHelpText; + private bool explicitShortName; + private object defaultValue; + private bool seenValue; + private FieldInfo field; + private Type elementType; + private ArgumentType flags; + private ArrayList collectionValues; + private ErrorReporter reporter; + private bool isDefault; + } + + private ArrayList arguments; + private Hashtable argumentMap; + private Argument defaultArgument; + private ErrorReporter reporter; + } +} \ No newline at end of file diff --git a/CpuToYourEars/CpuToYourEars.csproj b/CpuToYourEars/CpuToYourEars.csproj new file mode 100644 index 0000000..d58510f --- /dev/null +++ b/CpuToYourEars/CpuToYourEars.csproj @@ -0,0 +1,59 @@ + + + + + Debug + AnyCPU + {BE9E02BB-0939-4AE7-84F4-EDAA78FE6505} + Exe + AudioMonitor + CpuToYourEars + v4.6.1 + 512 + true + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + False + ..\Dependencies\Sanford.Multimedia.dll + + + False + ..\Dependencies\Sanford.Multimedia.Midi.dll + + + ..\packages\ShellProgress.1.0.0.0\lib\net45\ShellProgress.dll + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/CpuToYourEars/Program.cs b/CpuToYourEars/Program.cs new file mode 100644 index 0000000..ea4a05a --- /dev/null +++ b/CpuToYourEars/Program.cs @@ -0,0 +1,343 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Security.Principal; +using System.Threading; +using Microsoft.CommandLineHelper; +using Sanford.Multimedia.Midi; +using ShellProgress; + +namespace AudioMonitor +{ + public enum Notes + { + C = 60, + Cs = 61, + D = 62, + Ds = 63, + E = 64, + F = 65, + Fs = 66, + G = 67, + Gs = 68, + A = 69, + As = 70, + B = 71 + } + + public class Program + { + #region Private Variables + + private static OutputDevice outDevice; + private static PerformanceCounter cpuCounter; + private static Timer tmrCheckPerfCounter; + + private static readonly Chord Cmajor = new Chord(Notes.C, Notes.E, Notes.G); // c major + private static readonly Chord Dmajor = new Chord(Notes.D, Notes.Fs, Notes.A); // d major + private static readonly Chord Emajor = new Chord(Notes.E, Notes.Gs, Notes.B); // e major + private static readonly Chord Fmajor = new Chord(Notes.F, Notes.A, Notes.C); // f major + private static readonly Chord Gmajor = new Chord(Notes.G, Notes.B, Notes.D); // g major + private static readonly Chord Amajor = new Chord(Notes.A, Notes.Cs, Notes.E); // a major + private static readonly Chord Bmajor = new Chord(Notes.B, Notes.Ds, Notes.Fs); // b major + + private static Chord lastChord = null; + private static int lastNoteOffset = 0; + + private static readonly Arguments appArgs = new Arguments(); + private static WindowsImpersonationContext impersonationContext = null; + private static IProgressing appProgressBar = null; + + [DllImport("advapi32.dll")] + public static extern bool LogonUser(string name, string domain, string pass, int logType, int logpv, out IntPtr pht); + + #endregion + + static void Main(string[] args) + { + if (!Parser.ParseArgumentsWithUsage(args, appArgs)) + Environment.Exit(-1); + + try + { + AskForPassword(); + SetPerfCounter(); + if (!SetupMidiOutputDevice()) return; + SetTimer(); + SetupProgressBar(); + SetupWaitingToExit(); + } + catch (Exception e) + { + Console.WriteLine(e); + } + } + + #region Setup Application + + private static void AskForPassword() + { + // Ask for password if there is a passed in Domain Login, but no password + if (string.IsNullOrEmpty(appArgs.DomainLogin) || !string.IsNullOrEmpty(appArgs.Password)) return; + + string pass = ""; + Console.Write("Enter your password: "); + ConsoleKeyInfo key; + + do + { + key = Console.ReadKey(true); + + // Backspace Should Not Work + if (key.Key != ConsoleKey.Backspace && key.Key != ConsoleKey.Enter) + { + pass += key.KeyChar; + Console.Write("*"); + } + else + { + if (key.Key == ConsoleKey.Backspace && pass.Length > 0) + { + pass = pass.Substring(0, (pass.Length - 1)); + Console.Write("\b \b"); + } + } + } + // Stops Receving Keys Once Enter is Pressed + while (key.Key != ConsoleKey.Enter); + + appArgs.Password = pass; + Console.WriteLine(); + } + + private static void SetPerfCounter() + { + Console.WriteLine("Connecting to Perfmon Counter..."); + if (appArgs.DomainLogin.Length > 0) + { + LogonUser(appArgs.Login, appArgs.Domain, appArgs.Password, 9, 0, out var ptr); + WindowsIdentity windowsIdentity = new WindowsIdentity(ptr); + impersonationContext = windowsIdentity.Impersonate(); + } + + cpuCounter = string.IsNullOrEmpty(appArgs.Machine) ? + new PerformanceCounter("Processor", "% Processor Time", "_Total") : + new PerformanceCounter("Processor", "% Processor Time", "_Total", appArgs.Machine); + } + + private static bool SetupMidiOutputDevice() + { + if (OutputDevice.DeviceCount == 0) + { + Console.WriteLine("No MIDI output devices available."); + return false; + } + else + { + try + { + int outDeviceID = 0; + outDevice = new OutputDevice(outDeviceID); + } + catch (Exception e) + { + Console.WriteLine(e); + return false; + } + } + + return true; + } + + private static void SetTimer() + { + tmrCheckPerfCounter = new Timer(CheckPerfCounter, null, 100, 500); + } + + private static void SetupProgressBar() + { + var factory = new ProgressBarFactory(); + appProgressBar = factory.CreateInstance(100); + } + + private static void SetupWaitingToExit() + { + Console.WriteLine("Press ESC to stop"); + while (Console.ReadKey(true).Key == ConsoleKey.Escape) + { + ReleaseResources(); + Environment.Exit(0); + } + } + + #endregion + + #region Helper Methods + + private static void CheckPerfCounter(object state) + { + PlayBasedOnPerfCounter(); + } + + private static void PlayBasedOnPerfCounter() + { + float value = cpuCounter.NextValue(); + Debug.WriteLine(value); + + appProgressBar.Update((int) value); + + StopLastChord(); + + ChordToPlayBasedOnValue(value, out var chordToPlay, out var octaveToPlay); + PlayChord(chordToPlay, octaveToPlay); + } + + private static void ChordToPlayBasedOnValue(float value, out Chord chordToPlay, out int octaveToPlay) + { + octaveToPlay = 0; // set default + + if (value >= 0 && value < 10) + { + chordToPlay = Cmajor; + octaveToPlay = -2; + } + else if (value >= 10 && value < 20) + { + chordToPlay = Dmajor; + octaveToPlay = -1; + } + else if (value >= 20 && value < 30) + { + chordToPlay = Emajor; + octaveToPlay = -1; + } + else if (value >= 30 && value < 40) + { + chordToPlay = Fmajor; + } + else if (value >= 40 && value < 50) + { + chordToPlay = Gmajor; + } + else if (value >= 50 && value < 60) + { + chordToPlay = Amajor; + } + else if (value >= 60 && value < 70) + { + chordToPlay = Bmajor; + } + else if (value >= 70 && value < 80) + { + chordToPlay = Cmajor; + octaveToPlay = 1; + } + else if (value >= 80 && value < 90) + { + chordToPlay = Dmajor; + octaveToPlay = 2; + } + else + { + chordToPlay = Emajor; + octaveToPlay = 2; + } + } + + private static void PlayChord(Chord chord, int octave) + { + int noteOffset = (octave * 12); + outDevice.Send(new ChannelMessage(ChannelCommand.NoteOn, 0, (int)chord.Note1 + noteOffset, 127)); + outDevice.Send(new ChannelMessage(ChannelCommand.NoteOn, 0, (int)chord.Note2 + noteOffset, 127)); + outDevice.Send(new ChannelMessage(ChannelCommand.NoteOn, 0, (int)chord.Note3 + noteOffset, 127)); + + lastChord = chord; + lastNoteOffset = noteOffset; + } + + private static void StopLastChord() + { + if (lastChord != null) + { + outDevice.Send(new ChannelMessage(ChannelCommand.NoteOff, 0, (int)lastChord.Note1 + lastNoteOffset, 0)); + outDevice.Send(new ChannelMessage(ChannelCommand.NoteOff, 0, (int)lastChord.Note2 + lastNoteOffset, 0)); + outDevice.Send(new ChannelMessage(ChannelCommand.NoteOff, 0, (int)lastChord.Note3 + lastNoteOffset, 0)); + } + } + + #endregion + + #region Application Cleanup + + private static void ReleaseResources() + { + outDevice?.Dispose(); + + try + { + impersonationContext?.Undo(); + impersonationContext?.Dispose(); + } + catch + { + // nothing we can do + } + } + + #endregion + } + + [CommandLineArguments(CaseSensitive = false)] + public class Arguments + { + [Argument(ArgumentType.AtMostOnce, HelpText = "Machine Name", DefaultValue = "", LongName = "Machine", ShortName = "m")] + public string Machine; + [Argument(ArgumentType.AtMostOnce, HelpText = @"Domain Login, e.g CompanyDomain\username", DefaultValue = "", LongName = "DomainLogin", ShortName = "dl")] + public string DomainLogin; + [Argument(ArgumentType.AtMostOnce, HelpText = "Password", DefaultValue = "", LongName = "Password", ShortName = "p")] + public string Password; + + // helper properties + public string Domain + { + get + { + if (string.IsNullOrEmpty(DomainLogin)) return ""; + + var split = DomainLogin.Split(@"\".ToCharArray()); + if (split.Length == 2) + return split[0]; + + return ""; + } + } + + public string Login { + get + { + if (string.IsNullOrEmpty(DomainLogin)) return ""; + + var split = DomainLogin.Split(@"\".ToCharArray()); + if (split.Length == 2) + return split[1]; + + return ""; + } + } + } + + public class Chord + { + public Chord() {} + public Chord(Notes note1, Notes note2, Notes note3) + { + this.Note1 = note1; + this.Note2 = note2; + this.Note3 = note3; + } + public Notes Note1 { get; set; } + public Notes Note2 { get; set; } + public Notes Note3 { get; set; } + } +} diff --git a/CpuToYourEars/Properties/AssemblyInfo.cs b/CpuToYourEars/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..160a77f --- /dev/null +++ b/CpuToYourEars/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("AudioMonitor")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("HP Inc.")] +[assembly: AssemblyProduct("AudioMonitor")] +[assembly: AssemblyCopyright("Copyright © HP Inc. 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("be9e02bb-0939-4ae7-84f4-edaa78fe6505")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/CpuToYourEars/packages.config b/CpuToYourEars/packages.config new file mode 100644 index 0000000..3d8bf01 --- /dev/null +++ b/CpuToYourEars/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/Dependencies/Sanford.Collections.dll b/Dependencies/Sanford.Collections.dll new file mode 100644 index 0000000..43aaa54 Binary files /dev/null and b/Dependencies/Sanford.Collections.dll differ diff --git a/Dependencies/Sanford.Multimedia.Midi.dll b/Dependencies/Sanford.Multimedia.Midi.dll new file mode 100644 index 0000000..9f0f7d9 Binary files /dev/null and b/Dependencies/Sanford.Multimedia.Midi.dll differ diff --git a/Dependencies/Sanford.Multimedia.Timers.dll b/Dependencies/Sanford.Multimedia.Timers.dll new file mode 100644 index 0000000..bf91281 Binary files /dev/null and b/Dependencies/Sanford.Multimedia.Timers.dll differ diff --git a/Dependencies/Sanford.Multimedia.dll b/Dependencies/Sanford.Multimedia.dll new file mode 100644 index 0000000..d5a90a8 Binary files /dev/null and b/Dependencies/Sanford.Multimedia.dll differ diff --git a/Dependencies/Sanford.Threading.dll b/Dependencies/Sanford.Threading.dll new file mode 100644 index 0000000..e688f20 Binary files /dev/null and b/Dependencies/Sanford.Threading.dll differ diff --git a/DependenciesCode/Sanford.Multimedia.Midi/AssemblyInfo.cs b/DependenciesCode/Sanford.Multimedia.Midi/AssemblyInfo.cs new file mode 100644 index 0000000..aa080fc --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/AssemblyInfo.cs @@ -0,0 +1,61 @@ +using System; +using System.Reflection; +using System.Runtime.CompilerServices; + +// +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +// +[assembly: AssemblyTitle("MIDI Toolkit")] +[assembly: AssemblyDescription("A toolkit for creating MIDI applications.")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("")] +[assembly: AssemblyCopyright("Leslie Sanford 2006")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Revision and Build Numbers +// by using the '*' as shown below: + +[assembly: AssemblyVersion("5.0.0.0")] + +// +// In order to sign your assembly you must specify a key to use. Refer to the +// Microsoft .NET Framework documentation for more information on assembly signing. +// +// Use the attributes below to control which key is used for signing. +// +// Notes: +// (*) If no key is specified, the assembly is not signed. +// (*) KeyName refers to a key that has been installed in the Crypto Service +// Provider (CSP) on your machine. KeyFile refers to a file which contains +// a key. +// (*) If the KeyFile and the KeyName values are both specified, the +// following processing occurs: +// (1) If the KeyName can be found in the CSP, that key is used. +// (2) If the KeyName does not exist and the KeyFile does exist, the key +// in the KeyFile is installed into the CSP and used. +// (*) In order to create a KeyFile, you can use the sn.exe (Strong Name) utility. +// When specifying the KeyFile, the location of the KeyFile should be +// relative to the project output directory which is +// %Project Directory%\obj\. For example, if your KeyFile is +// located in the project directory, you would specify the AssemblyKeyFile +// attribute as [assembly: AssemblyKeyFile("..\\..\\mykey.snk")] +// (*) Delay Signing is an advanced option - see the Microsoft .NET Framework +// documentation for more information on this. +// +[assembly: AssemblyDelaySign(false)] +[assembly: AssemblyKeyFile("")] +[assembly: AssemblyKeyName("")] + +[assembly: CLSCompliant(true)] diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Clocks/IClock.cs b/DependenciesCode/Sanford.Multimedia.Midi/Clocks/IClock.cs new file mode 100644 index 0000000..049fc53 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Clocks/IClock.cs @@ -0,0 +1,89 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents functionality for generating events for driving Sequence playback. + /// + public interface IClock + { + #region IClock Members + + /// + /// Occurs when an IClock generates a tick. + /// + event EventHandler Tick; + + /// + /// Occurs when an IClock starts generating Ticks. + /// + /// + /// When an IClock is started, it resets itself and generates ticks to + /// drive playback from the beginning of the Sequence. + /// + event EventHandler Started; + + /// + /// Occurs when an IClock continues generating Ticks. + /// + /// + /// When an IClock is continued, it generates ticks to drive playback + /// from the current position within the Sequence. + /// + event EventHandler Continued; + + /// + /// Occurs when an IClock is stopped. + /// + event EventHandler Stopped; + + /// + /// Gets a value indicating whether the IClock is running. + /// + bool IsRunning + { + get; + } + + int Ticks + { + get; + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Clocks/MidiInternalClock.cs b/DependenciesCode/Sanford.Multimedia.Midi/Clocks/MidiInternalClock.cs new file mode 100644 index 0000000..bcc054d --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Clocks/MidiInternalClock.cs @@ -0,0 +1,384 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using Sanford.Multimedia.Timers; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Generates clock events internally. + /// + public class MidiInternalClock : PpqnClock, IComponent + { + #region MidiInternalClock Members + + #region Fields + + // Used for generating tick events. + private Timer timer = new Timer(); + + // Parses meta message tempo change messages. + private TempoChangeBuilder builder = new TempoChangeBuilder(); + + // Tick accumulator. + private int ticks = 0; + + // Indicates whether the clock has been disposed. + private bool disposed = false; + + private ISite site = null; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the MidiInternalClock class. + /// + public MidiInternalClock() : base(Timer.Capabilities.periodMin) + { + timer.Period = Timer.Capabilities.periodMin; + timer.Tick += new EventHandler(HandleTick); + } + + public MidiInternalClock(int timerPeriod) : base(timerPeriod) + { + timer.Period = timerPeriod; + timer.Tick += new EventHandler(HandleTick); + } + + /// + /// Initializes a new instance of the MidiInternalClock class with the + /// specified IContainer. + /// + /// + /// The IContainer to which the MidiInternalClock will add itself. + /// + public MidiInternalClock(IContainer container) : + base(Timer.Capabilities.periodMin) + { + /// + /// Required for Windows.Forms Class Composition Designer support + /// + container.Add(this); + + timer.Period = Timer.Capabilities.periodMin; + timer.Tick += new EventHandler(HandleTick); + } + + #endregion + + #region Methods + + /// + /// Starts the MidiInternalClock. + /// + public void Start() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("MidiInternalClock"); + } + + #endregion + + #region Guard + + if(running) + { + return; + } + + #endregion + + ticks = 0; + + Reset(); + + OnStarted(EventArgs.Empty); + + // Start the multimedia timer in order to start generating ticks. + timer.Start(); + + // Indicate that the clock is now running. + running = true; + + } + + /// + /// Resumes tick generation from the current position. + /// + public void Continue() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("MidiInternalClock"); + } + + #endregion + + #region Guard + + if(running) + { + return; + } + + #endregion + + // Raise Continued event. + OnContinued(EventArgs.Empty); + + // Start multimedia timer in order to start generating ticks. + timer.Start(); + + // Indicate that the clock is now running. + running = true; + } + + /// + /// Stops the MidiInternalClock. + /// + public void Stop() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("MidiInternalClock"); + } + + #endregion + + #region Guard + + if(!running) + { + return; + } + + #endregion + + // Stop the multimedia timer. + timer.Stop(); + + // Indicate that the clock is not running. + running = false; + + OnStopped(EventArgs.Empty); + } + + public void SetTicks(int ticks) + { + #region Require + + if(ticks < 0) + { + throw new ArgumentOutOfRangeException(); + } + + #endregion + + if(IsRunning) + { + Stop(); + } + + this.ticks = ticks; + + Reset(); + } + + public void Process(MetaMessage message) + { + #region Require + + if(message == null) + { + throw new ArgumentNullException("message"); + } + + #endregion + + #region Guard + + if(message.MetaType != MetaType.Tempo) + { + return; + } + + #endregion + + TempoChangeBuilder builder = new TempoChangeBuilder(message); + + // Set the new tempo. + Tempo = builder.Tempo; + } + + #region Event Raiser Methods + + protected virtual void OnDisposed(EventArgs e) + { + EventHandler handler = Disposed; + + if(handler != null) + { + handler(this, e); + } + } + + #endregion + + #region Event Handler Methods + + // Handles Tick events generated by the multimedia timer. + private void HandleTick(object sender, EventArgs e) + { + int t = GenerateTicks(); + + for(int i = 0; i < t; i++) + { + OnTick(EventArgs.Empty); + + ticks++; + } + } + + #endregion + + #endregion + + #region Properties + + /// + /// Gets or sets the tempo in microseconds per beat. + /// + public int Tempo + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("MidiInternalClock"); + } + + #endregion + + return GetTempo(); + } + set + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("MidiInternalClock"); + } + + #endregion + + SetTempo(value); + } + } + + public override int Ticks + { + get + { + return ticks; + } + } + + #endregion + + #endregion + + #region IComponent Members + + public event EventHandler Disposed; + + public ISite Site + { + get + { + return site; + } + set + { + site = value; + } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + #region Guard + + if(disposed) + { + return; + } + + #endregion + + if(running) + { + // Stop the multimedia timer. + timer.Stop(); + } + + disposed = true; + + timer.Dispose(); + + GC.SuppressFinalize(this); + + OnDisposed(EventArgs.Empty); + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Clocks/PpqnClock.cs b/DependenciesCode/Sanford.Multimedia.Midi/Clocks/PpqnClock.cs new file mode 100644 index 0000000..bda7f9c --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Clocks/PpqnClock.cs @@ -0,0 +1,264 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides basic functionality for generating tick events with pulses per + /// quarter note resolution. + /// + public abstract class PpqnClock : IClock + { + #region PpqnClock Members + + #region Fields + + /// + /// The default tempo in microseconds: 120bpm. + /// + public const int DefaultTempo = 500000; + + /// + /// The minimum pulses per quarter note value. + /// + public const int PpqnMinValue = 24; + + // The number of microseconds per millisecond. + private const int MicrosecondsPerMillisecond = 1000; + + // The pulses per quarter note value. + private int ppqn = PpqnMinValue; + + // The tempo in microseconds. + private int tempo = DefaultTempo; + + // The product of the timer period, the pulses per quarter note, and + // the number of microseconds per millisecond. + private int periodResolution; + + // The number of ticks per MIDI clock. + private int ticksPerClock; + + // The running fractional tick count. + private int fractionalTicks = 0; + + // The timer period. + private readonly int timerPeriod; + + // Indicates whether the clock is running. + protected bool running = false; + + #endregion + + #region Construction + + protected PpqnClock(int timerPeriod) + { + #region Require + + if(timerPeriod < 1) + { + throw new ArgumentOutOfRangeException("timerPeriod", timerPeriod, + "Timer period cannot be less than one."); + } + + #endregion + + this.timerPeriod = timerPeriod; + + CalculatePeriodResolution(); + CalculateTicksPerClock(); + } + + #endregion + + #region Methods + + protected int GetTempo() + { + return tempo; + } + + protected void SetTempo(int tempo) + { + #region Require + + if(tempo < 1) + { + throw new ArgumentOutOfRangeException( + "Tempo out of range."); + } + + #endregion + + this.tempo = tempo; + } + + protected void Reset() + { + fractionalTicks = 0; + } + + protected int GenerateTicks() + { + int ticks = (fractionalTicks + periodResolution) / tempo; + fractionalTicks += periodResolution - ticks * tempo; + + return ticks; + } + + private void CalculatePeriodResolution() + { + periodResolution = ppqn * timerPeriod * MicrosecondsPerMillisecond; + } + + private void CalculateTicksPerClock() + { + ticksPerClock = ppqn / PpqnMinValue; + } + + protected virtual void OnTick(EventArgs e) + { + EventHandler handler = Tick; + + if(handler != null) + { + handler(this, EventArgs.Empty); + } + } + + protected virtual void OnStarted(EventArgs e) + { + EventHandler handler = Started; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnStopped(EventArgs e) + { + EventHandler handler = Stopped; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnContinued(EventArgs e) + { + EventHandler handler = Continued; + + if(handler != null) + { + handler(this, e); + } + } + + #endregion + + #region Properties + + public int Ppqn + { + get + { + return ppqn; + } + set + { + #region Require + + if(value < PpqnMinValue) + { + throw new ArgumentOutOfRangeException("Ppqn", value, + "Pulses per quarter note out of range."); + } + else if(value % PpqnMinValue != 0) + { + throw new ArgumentException( + "Pulses per quarter note is not a multiple of 24."); + } + + #endregion + + ppqn = value; + + CalculatePeriodResolution(); + CalculateTicksPerClock(); + } + } + + public abstract int Ticks + { + get; + } + + public int TicksPerClock + { + get + { + return ticksPerClock; + } + } + + #endregion + + #endregion + + #region IClock Members + + public event System.EventHandler Tick; + + public event System.EventHandler Started; + + public event System.EventHandler Continued; + + public event System.EventHandler Stopped; + + public bool IsRunning + { + get + { + return running; + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Construction.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Construction.cs new file mode 100644 index 0000000..ca6791f --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Construction.cs @@ -0,0 +1,75 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System.Threading; +using Sanford.Threading; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice : MidiDevice + { + #region Construction + + /// + /// Initializes a new instance of the InputDevice class with the + /// specified device ID. + /// + public InputDevice(int deviceID) : base(deviceID) + { + midiInProc = HandleMessage; + + int result = midiInOpen(ref handle, deviceID, midiInProc, 0, CALLBACK_FUNCTION); + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + delegateQueue = new DelegateQueue(); + } + else + { + throw new InputDeviceException(result); + } + } + + ~InputDevice() + { + if(!IsDisposed) + { + midiInReset(Handle); + midiInClose(Handle); + } + } + + #endregion + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Events.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Events.cs new file mode 100644 index 0000000..0e1a377 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Events.cs @@ -0,0 +1,97 @@ +using System; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice + { + public event EventHandler ChannelMessageReceived; + + public event EventHandler SysExMessageReceived; + + public event EventHandler SysCommonMessageReceived; + + public event EventHandler SysRealtimeMessageReceived; + + public event EventHandler InvalidShortMessageReceived; + + public event EventHandler InvalidSysExMessageReceived; + + protected virtual void OnChannelMessageReceived(ChannelMessageEventArgs e) + { + EventHandler handler = ChannelMessageReceived; + + if(handler != null) + { + context.Post(delegate(object dummy) + { + handler(this, e); + }, null); + } + } + + protected virtual void OnSysExMessageReceived(SysExMessageEventArgs e) + { + EventHandler handler = SysExMessageReceived; + + if(handler != null) + { + context.Post(delegate(object dummy) + { + handler(this, e); + }, null); + } + } + + protected virtual void OnSysCommonMessageReceived(SysCommonMessageEventArgs e) + { + EventHandler handler = SysCommonMessageReceived; + + if(handler != null) + { + context.Post(delegate(object dummy) + { + handler(this, e); + }, null); + } + } + + protected virtual void OnSysRealtimeMessageReceived(SysRealtimeMessageEventArgs e) + { + EventHandler handler = SysRealtimeMessageReceived; + + if(handler != null) + { + context.Post(delegate(object dummy) + { + handler(this, e); + }, null); + } + } + + protected virtual void OnInvalidShortMessageReceived(InvalidShortMessageEventArgs e) + { + EventHandler handler = InvalidShortMessageReceived; + + if(handler != null) + { + context.Post(delegate(object dummy) + { + handler(this, e); + }, null); + } + } + + protected virtual void OnInvalidSysExMessageReceived(InvalidSysExMessageEventArgs e) + { + EventHandler handler = InvalidSysExMessageReceived; + + if(handler != null) + { + context.Post(delegate(object dummy) + { + handler(this, e); + }, null); + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Fields.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Fields.cs new file mode 100644 index 0000000..fd618ab --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Fields.cs @@ -0,0 +1,71 @@ +#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.Collections.Generic; +using System.ComponentModel; +using System.Threading; +using Sanford.Threading; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice + { + private delegate void GenericDelegate(T args); + + private DelegateQueue delegateQueue = null; + + private volatile int bufferCount = 0; + + private readonly object lockObject = new object(); + + private MidiInProc midiInProc; + + private bool recording = false; + + private MidiHeaderBuilder headerBuilder = new MidiHeaderBuilder(); + + private ChannelMessageBuilder cmBuilder = new ChannelMessageBuilder(); + + private SysCommonMessageBuilder scBuilder = new SysCommonMessageBuilder(); + + private int handle = 0; + + private volatile bool resetting = false; + + private int sysExBufferSize = 4096; + + private List sysExData = new List(); + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Messaging.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Messaging.cs new file mode 100644 index 0000000..704d3db --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Messaging.cs @@ -0,0 +1,277 @@ +#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.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Sanford.Multimedia; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice : MidiDevice + { + private void HandleMessage(int handle, int msg, int instance, int param1, int param2) + { + if(msg == MIM_OPEN) + { + } + else if(msg == MIM_CLOSE) + { + } + else if(msg == MIM_DATA) + { + delegateQueue.Post(HandleShortMessage, param1); + } + else if(msg == MIM_MOREDATA) + { + delegateQueue.Post(HandleShortMessage, param1); + } + else if(msg == MIM_LONGDATA) + { + delegateQueue.Post(HandleSysExMessage, new IntPtr(param1)); + } + else if(msg == MIM_ERROR) + { + delegateQueue.Post(HandleInvalidShortMessage, param1); + } + else if(msg == MIM_LONGERROR) + { + delegateQueue.Post(HandleInvalidSysExMessage, new IntPtr(param1)); + } + } + + private void HandleShortMessage(object state) + { + int message = (int)state; + int status = ShortMessage.UnpackStatus(message); + + if(status >= (int)ChannelCommand.NoteOff && + status <= (int)ChannelCommand.PitchWheel + + ChannelMessage.MidiChannelMaxValue) + { + cmBuilder.Message = message; + cmBuilder.Build(); + + OnChannelMessageReceived(new ChannelMessageEventArgs(cmBuilder.Result)); + } + else if(status == (int)SysCommonType.MidiTimeCode || + status == (int)SysCommonType.SongPositionPointer || + status == (int)SysCommonType.SongSelect || + status == (int)SysCommonType.TuneRequest) + { + scBuilder.Message = message; + scBuilder.Build(); + + OnSysCommonMessageReceived(new SysCommonMessageEventArgs(scBuilder.Result)); + } + else + { + SysRealtimeMessageEventArgs e = null; + + switch((SysRealtimeType)status) + { + case SysRealtimeType.ActiveSense: + e = SysRealtimeMessageEventArgs.ActiveSense; + break; + + case SysRealtimeType.Clock: + e = SysRealtimeMessageEventArgs.Clock; + break; + + case SysRealtimeType.Continue: + e = SysRealtimeMessageEventArgs.Continue; + break; + + case SysRealtimeType.Reset: + e = SysRealtimeMessageEventArgs.Reset; + break; + + case SysRealtimeType.Start: + e = SysRealtimeMessageEventArgs.Start; + break; + + case SysRealtimeType.Stop: + e = SysRealtimeMessageEventArgs.Stop; + break; + + case SysRealtimeType.Tick: + e = SysRealtimeMessageEventArgs.Tick; + break; + } + + OnSysRealtimeMessageReceived(e); + } + } + + private void HandleSysExMessage(object state) + { + lock(lockObject) + { + IntPtr headerPtr = (IntPtr)state; + + MidiHeader header = (MidiHeader)Marshal.PtrToStructure(headerPtr, typeof(MidiHeader)); + + if(!resetting) + { + for(int i = 0; i < header.bytesRecorded; i++) + { + sysExData.Add(Marshal.ReadByte(header.data, i)); + } + + if(sysExData[0] == 0xF0 && sysExData[sysExData.Count - 1] == 0xF7) + { + SysExMessage message = new SysExMessage(sysExData.ToArray()); + + sysExData.Clear(); + + OnSysExMessageReceived(new SysExMessageEventArgs(message)); + } + + int result = AddSysExBuffer(); + + if(result != DeviceException.MMSYSERR_NOERROR) + { + Exception ex = new InputDeviceException(result); + + OnError(new ErrorEventArgs(ex)); + } + } + + ReleaseBuffer(headerPtr); + } + } + + private void HandleInvalidShortMessage(object state) + { + OnInvalidShortMessageReceived(new InvalidShortMessageEventArgs((int)state)); + } + + private void HandleInvalidSysExMessage(object state) + { + lock(lockObject) + { + IntPtr headerPtr = (IntPtr)state; + + MidiHeader header = (MidiHeader)Marshal.PtrToStructure(headerPtr, typeof(MidiHeader)); + + if(!resetting) + { + byte[] data = new byte[header.bytesRecorded]; + + Marshal.Copy(header.data, data, 0, data.Length); + + OnInvalidSysExMessageReceived(new InvalidSysExMessageEventArgs(data)); + + int result = AddSysExBuffer(); + + if(result != DeviceException.MMSYSERR_NOERROR) + { + Exception ex = new InputDeviceException(result); + + OnError(new ErrorEventArgs(ex)); + } + } + + ReleaseBuffer(headerPtr); + } + } + + private void ReleaseBuffer(IntPtr headerPtr) + { + int result = midiInUnprepareHeader(Handle, headerPtr, SizeOfMidiHeader); + + if(result != DeviceException.MMSYSERR_NOERROR) + { + Exception ex = new InputDeviceException(result); + + OnError(new ErrorEventArgs(ex)); + } + + headerBuilder.Destroy(headerPtr); + + bufferCount--; + + Debug.Assert(bufferCount >= 0); + + Monitor.Pulse(lockObject); + } + + public int AddSysExBuffer() + { + int result; + + // Initialize the MidiHeader builder. + headerBuilder.BufferLength = sysExBufferSize; + headerBuilder.Build(); + + // Get the pointer to the built MidiHeader. + IntPtr headerPtr = headerBuilder.Result; + + // Prepare the header to be used. + result = midiInPrepareHeader(Handle, headerPtr, SizeOfMidiHeader); + + // If the header was perpared successfully. + if(result == DeviceException.MMSYSERR_NOERROR) + { + bufferCount++; + + // Add the buffer to the InputDevice. + result = midiInAddBuffer(Handle, headerPtr, SizeOfMidiHeader); + + // If the buffer could not be added. + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + // Unprepare header - there's a chance that this will fail + // for whatever reason, but there's not a lot that can be + // done at this point. + midiInUnprepareHeader(Handle, headerPtr, SizeOfMidiHeader); + + bufferCount--; + + // Destroy header. + headerBuilder.Destroy(); + } + } + // Else the header could not be prepared. + else + { + // Destroy header. + headerBuilder.Destroy(); + } + + return result; + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Properties.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Properties.cs new file mode 100644 index 0000000..9fa9560 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Properties.cs @@ -0,0 +1,79 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice + { + public override int Handle + { + get + { + return handle; + } + } + + public int SysExBufferSize + { + get + { + return sysExBufferSize; + } + set + { + #region Require + + if(value < 1) + { + throw new ArgumentOutOfRangeException(); + } + + #endregion + + sysExBufferSize = value; + } + } + + public static int DeviceCount + { + get + { + return midiInGetNumDevs(); + } + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.PublicMethods.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.PublicMethods.cs new file mode 100644 index 0000000..8e86d28 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.PublicMethods.cs @@ -0,0 +1,213 @@ +#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.Diagnostics; +using System.Threading; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice + { + public override void Close() + { + #region Guard + + if(IsDisposed) + { + return; + } + + #endregion + + Dispose(true); + } + + public void StartRecording() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("InputDevice"); + } + + #endregion + + #region Guard + + if(recording) + { + return; + } + + #endregion + + lock(lockObject) + { + int result = AddSysExBuffer(); + + if(result == DeviceException.MMSYSERR_NOERROR) + { + result = AddSysExBuffer(); + } + + if(result == DeviceException.MMSYSERR_NOERROR) + { + result = AddSysExBuffer(); + } + + if(result == DeviceException.MMSYSERR_NOERROR) + { + result = AddSysExBuffer(); + } + + if(result == DeviceException.MMSYSERR_NOERROR) + { + result = midiInStart(Handle); + } + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + recording = true; + } + else + { + throw new InputDeviceException(result); + } + } + } + + public void StopRecording() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("InputDevice"); + } + + #endregion + + #region Guard + + if(!recording) + { + return; + } + + #endregion + + lock(lockObject) + { + int result = midiInStop(Handle); + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + recording = false; + } + else + { + throw new InputDeviceException(result); + } + } + } + + public override void Reset() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("InputDevice"); + } + + #endregion + + lock(lockObject) + { + resetting = true; + + int result = midiInReset(Handle); + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + recording = false; + + while(bufferCount > 0) + { + Monitor.Wait(lockObject); + } + + resetting = false; + } + else + { + resetting = false; + + throw new InputDeviceException(result); + } + } + } + + public static MidiInCaps GetDeviceCapabilities(int deviceID) + { + int result; + MidiInCaps caps = new MidiInCaps(); + + result = midiInGetDevCaps(deviceID, ref caps, SizeOfMidiHeader); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new InputDeviceException(result); + } + + return caps; + } + + public override void Dispose() + { + #region Guard + + if(IsDisposed) + { + return; + } + + #endregion + + Dispose(true); + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Win32.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Win32.cs new file mode 100644 index 0000000..a0846cb --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.Win32.cs @@ -0,0 +1,95 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Runtime.InteropServices; + +namespace Sanford.Multimedia.Midi +{ + public partial class InputDevice + { + // Represents the method that handles messages from Windows. + private delegate void MidiInProc(int handle, int msg, int instance, int param1, int param2); + + #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 midiInStop(int handle); + + [DllImport("winmm.dll")] + private static extern int midiInReset(int handle); + + [DllImport("winmm.dll")] + private static extern int midiInPrepareHeader(int handle, + IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + private static extern int midiInUnprepareHeader(int handle, + IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + private static extern int midiInAddBuffer(int handle, + IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + private static extern int midiInGetDevCaps(int deviceID, + ref MidiInCaps caps, int sizeOfMidiInCaps); + + [DllImport("winmm.dll")] + private static extern int midiInGetNumDevs(); + + private const int MIDI_IO_STATUS = 0x00000020; + + private const int MIM_OPEN = 0x3C1; + private const int MIM_CLOSE = 0x3C2; + private const int MIM_DATA = 0x3C3; + private const int MIM_LONGDATA = 0x3C4; + private const int MIM_ERROR = 0x3C5; + private const int MIM_LONGERROR = 0x3C6; + private const int MIM_MOREDATA = 0x3CC; + private const int MHDR_DONE = 0x00000001; + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.cs new file mode 100644 index 0000000..216b2d6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/InputDevice.cs @@ -0,0 +1,134 @@ +#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.Runtime.InteropServices; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents a MIDI device capable of receiving MIDI events. + /// + public partial class InputDevice : MidiDevice + { + protected override void Dispose(bool disposing) + { + if(disposing) + { + lock(lockObject) + { + Reset(); + + int result = midiInClose(handle); + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + delegateQueue.Dispose(); + } + else + { + throw new InputDeviceException(result); + } + } + } + else + { + midiInReset(Handle); + midiInClose(Handle); + } + + base.Dispose(disposing); + } + } + + /// + /// The exception that is thrown when a error occurs with the InputDevice + /// class. + /// + public class InputDeviceException : MidiDeviceException + { + #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 + + /// + /// Initializes a new instance of the InputDeviceException class with + /// the specified error code. + /// + /// + /// The error code. + /// + public InputDeviceException(int errCode) : base(errCode) + { + // Get error message. + midiInGetErrorText(errCode, errMsg, errMsg.Capacity); + } + + #endregion + + #region Properties + + /// + /// Gets a message that describes the current exception. + /// + public override string Message + { + get + { + return errMsg.ToString(); + } + } + + #endregion + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/MidiInCaps.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/MidiInCaps.cs new file mode 100644 index 0000000..d36da93 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/InputDevice Class/MidiInCaps.cs @@ -0,0 +1,79 @@ +#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.Runtime.InteropServices; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents MIDI input device capabilities. + /// + [StructLayout(LayoutKind.Sequential)] + public struct MidiInCaps + { + #region MidiInCaps Members + + /// + /// Manufacturer identifier of the device driver for the Midi output + /// device. + /// + public short mid; + + /// + /// Product identifier of the Midi output device. + /// + public short pid; + + /// + /// 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. + /// + public int driverVersion; + + /// + /// Product name. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string name; + + /// + /// Optional functionality supported by the device. + /// + public int support; + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiDevice.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiDevice.cs new file mode 100644 index 0000000..b337739 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiDevice.cs @@ -0,0 +1,120 @@ +#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.Runtime.InteropServices; +using System.Threading; +using Sanford.Multimedia; + +namespace Sanford.Multimedia.Midi +{ + /// + /// The base class for all MIDI devices. + /// + public abstract class MidiDevice : Device + { + #region MidiDevice Members + + #region Win32 Midi Device Functions + + [DllImport("winmm.dll")] + private static extern int midiConnect(int handleA, int handleB, int reserved); + + [DllImport("winmm.dll")] + private static extern int midiDisconnect(int handleA, int handleB, int reserved); + + #endregion + + // Size of the MidiHeader structure. + protected static readonly int SizeOfMidiHeader; + + static MidiDevice() + { + SizeOfMidiHeader = Marshal.SizeOf(typeof(MidiHeader)); + } + + public MidiDevice(int deviceID) : base(deviceID) + { + } + + /// + /// Connects a MIDI InputDevice to a MIDI thru or OutputDevice, or + /// connects a MIDI thru device to a MIDI OutputDevice. + /// + /// + /// Handle to a MIDI InputDevice or a MIDI thru device (for thru + /// devices, this handle must belong to a MIDI OutputDevice). + /// + /// + /// Handle to the MIDI OutputDevice or thru device. + /// + /// + /// If an error occurred while connecting the two devices. + /// + public static void Connect(int handleA, int handleB) + { + int result = midiConnect(handleA, handleB, 0); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new MidiDeviceException(result); + } + } + + /// + /// Disconnects a MIDI InputDevice from a MIDI thru or OutputDevice, or + /// disconnects a MIDI thru device from a MIDI OutputDevice. + /// + /// + /// Handle to a MIDI InputDevice or a MIDI thru device. + /// + /// + /// Handle to the MIDI OutputDevice to be disconnected. + /// + /// + /// If an error occurred while disconnecting the two devices. + /// + public static void Disconnect(int handleA, int handleB) + { + int result = midiDisconnect(handleA, handleB, 0); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new MidiDeviceException(result); + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiDeviceException.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiDeviceException.cs new file mode 100644 index 0000000..3b80ce2 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiDeviceException.cs @@ -0,0 +1,73 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + /// + /// The base class for all MIDI device exception classes. + /// + public class MidiDeviceException : DeviceException + { + #region Error Codes + + public const int MIDIERR_UNPREPARED = 64; /* header not prepared */ + public const int MIDIERR_STILLPLAYING = 65; /* still something playing */ + public const int MIDIERR_NOMAP = 66; /* no configured instruments */ + public const int MIDIERR_NOTREADY = 67; /* hardware is still busy */ + public const int MIDIERR_NODEVICE = 68; /* port no longer connected */ + public const int MIDIERR_INVALIDSETUP = 69; /* invalid MIF */ + public const int MIDIERR_BADOPENMODE = 70; /* operation unsupported w/ open mode */ + public const int MIDIERR_DONT_CONTINUE = 71; /* thru device 'eating' a message */ + public const int MIDIERR_LASTERROR = 71; /* last error in range */ + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the DeviceException class with the + /// specified error code. + /// + /// + /// The error code. + /// + public MidiDeviceException(int errCode) : base(errCode) + { + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiHeader.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiHeader.cs new file mode 100644 index 0000000..7bc4ed6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiHeader.cs @@ -0,0 +1,101 @@ +#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.Runtime.InteropServices; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents the Windows Multimedia MIDIHDR structure. + /// + [StructLayout(LayoutKind.Sequential)] + internal struct MidiHeader + { + #region MidiHeader Members + + /// + /// Pointer to MIDI data. + /// + public IntPtr data; + + /// + /// Size of the buffer. + /// + public int bufferLength; + + /// + /// Actual amount of data in the buffer. This value should be less than + /// or equal to the value given in the dwBufferLength member. + /// + public int bytesRecorded; + + /// + /// Custom user data. + /// + public int user; + + /// + /// Flags giving information about the buffer. + /// + public int flags; + + /// + /// Reserved; do not use. + /// + public IntPtr next; + + /// + /// Reserved; do not use. + /// + public int reserved; + + /// + /// Offset into the buffer when a callback is performed. (This + /// callback is generated because the MEVT_F_CALLBACK flag is + /// set in the dwEvent member of the MidiEventArgs structure.) + /// This offset enables an application to determine which + /// event caused the callback. + /// + public int offset; + + /// + /// Reserved; do not use. + /// + [MarshalAs(UnmanagedType.ByValArray, SizeConst=4)] + public int[] reservedArray; + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiHeaderBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiHeaderBuilder.cs new file mode 100644 index 0000000..f45c862 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/MidiHeaderBuilder.cs @@ -0,0 +1,248 @@ +#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.Collections; +using System.Runtime.InteropServices; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Builds a pointer to a MidiHeader structure. + /// + internal class MidiHeaderBuilder + { + // The length of the system exclusive buffer. + private int bufferLength; + + // The system exclusive data. + private byte[] data; + + // Indicates whether the pointer to the MidiHeader has been built. + private bool built = false; + + // The built pointer to the MidiHeader. + private IntPtr result; + + /// + /// Initializes a new instance of the MidiHeaderBuilder. + /// + public MidiHeaderBuilder() + { + BufferLength = 1; + } + + #region Methods + + /// + /// Builds the pointer to the MidiHeader structure. + /// + public void Build() + { + MidiHeader header = new MidiHeader(); + + // Initialize the MidiHeader. + header.bufferLength = BufferLength; + header.bytesRecorded = 0; + header.data = Marshal.AllocHGlobal(BufferLength); + header.flags = 0; + + // Write data to the MidiHeader. + for(int i = 0; i < BufferLength; i++) + { + Marshal.WriteByte(header.data, i, data[i]); + } + + try + { + result = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MidiHeader))); + } + catch(Exception) + { + Marshal.FreeHGlobal(header.data); + + throw; + } + + try + { + Marshal.StructureToPtr(header, result, false); + } + catch(Exception) + { + Marshal.FreeHGlobal(header.data); + Marshal.FreeHGlobal(result); + + throw; + } + + built = true; + } + + /// + /// Initializes the MidiHeaderBuilder with the specified SysExMessage. + /// + /// + /// The SysExMessage to use for initializing the MidiHeaderBuilder. + /// + public void InitializeBuffer(SysExMessage message) + { + // If this is a start system exclusive message. + if(message.SysExType == SysExType.Start) + { + BufferLength = message.Length; + + // Copy entire message. + for(int i = 0; i < BufferLength; i++) + { + data[i] = message[i]; + } + } + // Else this is a continuation message. + else + { + BufferLength = message.Length - 1; + + // Copy all but the first byte of message. + for(int i = 0; i < BufferLength; i++) + { + data[i] = message[i + 1]; + } + } + } + + public void InitializeBuffer(ICollection events) + { + #region Require + + if(events == null) + { + throw new ArgumentNullException("events"); + } + else if(events.Count % 4 != 0) + { + throw new ArgumentException("Stream events not word aligned."); + } + + #endregion + + #region Guard + + if(events.Count == 0) + { + return; + } + + #endregion + + BufferLength = events.Count; + + events.CopyTo(data, 0); + } + + /// + /// Releases the resources associated with the built MidiHeader pointer. + /// + public void Destroy() + { + #region Require + + if(!built) + { + throw new InvalidOperationException("Cannot destroy MidiHeader"); + } + + #endregion + + Destroy(result); + } + + /// + /// Releases the resources associated with the specified MidiHeader pointer. + /// + /// + /// The MidiHeader pointer. + /// + public void Destroy(IntPtr headerPtr) + { + MidiHeader header = (MidiHeader)Marshal.PtrToStructure(headerPtr, typeof(MidiHeader)); + + Marshal.FreeHGlobal(header.data); + Marshal.FreeHGlobal(headerPtr); + } + + #endregion + + #region Properties + + /// + /// The length of the system exclusive buffer. + /// + public int BufferLength + { + get + { + return bufferLength; + } + set + { + #region Require + + if(value <= 0) + { + throw new ArgumentOutOfRangeException("BufferLength", value, + "MIDI header buffer length out of range."); + } + + #endregion + + bufferLength = value; + data = new byte[value]; + } + } + + /// + /// Gets the pointer to the MidiHeader. + /// + public IntPtr Result + { + get + { + return result; + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/MidiOutCaps.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/MidiOutCaps.cs new file mode 100644 index 0000000..bfab960 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/MidiOutCaps.cs @@ -0,0 +1,107 @@ +#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.Runtime.InteropServices; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents MIDI output device capabilities. + /// + [StructLayout(LayoutKind.Sequential)] + public struct MidiOutCaps + { + #region MidiOutCaps Members + + /// + /// Manufacturer identifier of the device driver for the Midi output + /// device. + /// + public short mid; + + /// + /// Product identifier of the Midi output device. + /// + public short pid; + + /// + /// 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. + /// + public int driverVersion; + + /// + /// Product name. + /// + [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] + public string name; + + /// + /// Flags describing the type of the Midi output device. + /// + public short technology; + + /// + /// Number of voices supported by an internal synthesizer device. If + /// the device is a port, this member is not meaningful and is set + /// to 0. + /// + public short voices; + + /// + /// Maximum number of simultaneous notes that can be played by an + /// internal synthesizer device. If the device is a port, this member + /// is not meaningful and is set to 0. + /// + public short notes; + + /// + /// Channels that an internal synthesizer device responds to, where the + /// least significant bit refers to channel 0 and the most significant + /// bit to channel 15. Port devices that transmit on all channels set + /// this member to 0xFFFF. + /// + public short channelMask; + + /// + /// Optional functionality supported by the device. + /// + public int support; + + #endregion + } + +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/NoOpEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/NoOpEventArgs.cs new file mode 100644 index 0000000..ceed7af --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/NoOpEventArgs.cs @@ -0,0 +1,59 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; + +namespace Sanford.Multimedia.Midi +{ + /// + /// + /// + public class NoOpEventArgs : EventArgs + { + private int data; + + public NoOpEventArgs(int data) + { + this.data = data; + } + + public int Data + { + get + { + return data; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputDevice.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputDevice.cs new file mode 100644 index 0000000..6484cd3 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputDevice.cs @@ -0,0 +1,297 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents a device capable of sending MIDI messages. + /// + public sealed class OutputDevice : OutputDeviceBase + { + #region Win32 Midi Output Functions and Constants + + [DllImport("winmm.dll")] + private static extern int midiOutOpen(ref int handle, int deviceID, + MidiOutProc proc, int instance, int flags); + + [DllImport("winmm.dll")] + private static extern int midiOutClose(int handle); + + #endregion + + private MidiOutProc midiOutProc; + + private bool runningStatusEnabled = false; + + private int runningStatus = 0; + + #region Construction + + /// + /// Initializes a new instance of the OutputDevice class. + /// + public OutputDevice(int deviceID) : base(deviceID) + { + midiOutProc = HandleMessage; + + int result = midiOutOpen(ref hndle, deviceID, midiOutProc, 0, CALLBACK_FUNCTION); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + + #endregion + + protected override void Dispose(bool disposing) + { + if(disposing) + { + lock(lockObject) + { + Reset(); + + // Close the OutputDevice. + int result = midiOutClose(Handle); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + // Throw an exception. + throw new OutputDeviceException(result); + } + } + } + else + { + midiOutReset(Handle); + midiOutClose(Handle); + } + + base.Dispose(disposing); + } + + /// + /// Closes the OutputDevice. + /// + /// + /// If an error occurred while closing the OutputDevice. + /// + public override void Close() + { + #region Guard + + if(IsDisposed) + { + return; + } + + #endregion + + Dispose(true); + } + + /// + /// Resets the OutputDevice. + /// + public override void Reset() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + runningStatus = 0; + + base.Reset(); + } + + public override void Send(ChannelMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + lock(lockObject) + { + // If running status is enabled. + if(runningStatusEnabled) + { + // If the message's status value matches the running status. + if(message.Status == runningStatus) + { + // Send only the two data bytes without the status byte. + Send(message.Message >> 8); + } + // Else the message's status value does not match the running + // status. + else + { + // Send complete message with status byte. + Send(message.Message); + + // Update running status. + runningStatus = message.Status; + } + } + // Else running status has not been enabled. + else + { + Send(message.Message); + } + } + } + + public override void Send(SysExMessage message) + { + // System exclusive cancels running status. + runningStatus = 0; + + base.Send(message); + } + + public override void Send(SysCommonMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + // System common cancels running status. + runningStatus = 0; + + base.Send(message); + } + + #region Properties + + /// + /// Gets or sets a value indicating whether the OutputDevice uses + /// a running status. + /// + public bool RunningStatusEnabled + { + get + { + return runningStatusEnabled; + } + set + { + runningStatusEnabled = value; + + // Reset running status. + runningStatus = 0; + } + } + + #endregion + } + + /// + /// The exception that is thrown when a error occurs with the OutputDevice + /// class. + /// + public class OutputDeviceException : MidiDeviceException + { + #region OutputDeviceException Members + + #region Win32 Midi Output Error Function + + [DllImport("winmm.dll")] + private static extern int midiOutGetErrorText(int errCode, + StringBuilder message, int sizeOfMessage); + + #endregion + + #region Fields + + // The error message. + private StringBuilder message = new StringBuilder(128); + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the OutputDeviceException class with + /// the specified error code. + /// + /// + /// The error code. + /// + public OutputDeviceException(int errCode) : base(errCode) + { + // Get error message. + midiOutGetErrorText(errCode, message, message.Capacity); + } + + #endregion + + #region Properties + + /// + /// Gets a message that describes the current exception. + /// + public override string Message + { + get + { + return message.ToString(); + } + } + + #endregion + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputDeviceBase.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputDeviceBase.cs new file mode 100644 index 0000000..d29d2b7 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputDeviceBase.cs @@ -0,0 +1,342 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; +using Sanford.Threading; + +namespace Sanford.Multimedia.Midi +{ + public abstract class OutputDeviceBase : MidiDevice + { + [DllImport("winmm.dll")] + protected static extern int midiOutReset(int handle); + + [DllImport("winmm.dll")] + protected static extern int midiOutShortMsg(int handle, int message); + + [DllImport("winmm.dll")] + protected static extern int midiOutPrepareHeader(int handle, + IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + protected static extern int midiOutUnprepareHeader(int handle, + IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + protected static extern int midiOutLongMsg(int handle, + IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + protected static extern int midiOutGetDevCaps(int deviceID, + ref MidiOutCaps caps, int sizeOfMidiOutCaps); + + [DllImport("winmm.dll")] + protected static extern int midiOutGetNumDevs(); + + protected const int MOM_OPEN = 0x3C7; + protected const int MOM_CLOSE = 0x3C8; + protected const int MOM_DONE = 0x3C9; + + protected delegate void GenericDelegate(T args); + + // Represents the method that handles messages from Windows. + protected delegate void MidiOutProc(int handle, int msg, int instance, int param1, int param2); + + // For releasing buffers. + protected DelegateQueue delegateQueue = new DelegateQueue(); + + protected readonly object lockObject = new object(); + + // The number of buffers still in the queue. + protected int bufferCount = 0; + + // Builds MidiHeader structures for sending system exclusive messages. + private MidiHeaderBuilder headerBuilder = new MidiHeaderBuilder(); + + // The device handle. + protected int hndle = 0; + + public OutputDeviceBase(int deviceID) : base(deviceID) + { + } + + ~OutputDeviceBase() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + if(disposing) + { + delegateQueue.Dispose(); + } + + base.Dispose(disposing); + } + + public virtual void Send(ChannelMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + Send(message.Message); + } + + public virtual void Send(SysExMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + lock(lockObject) + { + headerBuilder.InitializeBuffer(message); + headerBuilder.Build(); + + // Prepare system exclusive buffer. + int result = midiOutPrepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader); + + // If the system exclusive buffer was prepared successfully. + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + bufferCount++; + + // Send system exclusive message. + result = midiOutLongMsg(Handle, headerBuilder.Result, SizeOfMidiHeader); + + // If the system exclusive message could not be sent. + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + midiOutUnprepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader); + bufferCount--; + headerBuilder.Destroy(); + + // Throw an exception. + throw new OutputDeviceException(result); + } + } + // Else the system exclusive buffer could not be prepared. + else + { + // Destroy system exclusive buffer. + headerBuilder.Destroy(); + + // Throw an exception. + throw new OutputDeviceException(result); + } + } + } + + public virtual void Send(SysCommonMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + Send(message.Message); + } + + public virtual void Send(SysRealtimeMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + Send(message.Message); + } + + public override void Reset() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + lock(lockObject) + { + // Reset the OutputDevice. + int result = midiOutReset(Handle); + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + while(bufferCount > 0) + { + Monitor.Wait(lockObject); + } + } + else + { + // Throw an exception. + throw new OutputDeviceException(result); + } + } + } + + protected void Send(int message) + { + lock(lockObject) + { + int result = midiOutShortMsg(Handle, message); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + + public static MidiOutCaps GetDeviceCapabilities(int deviceID) + { + MidiOutCaps caps = new MidiOutCaps(); + + // Get the device's capabilities. + int result = midiOutGetDevCaps(deviceID, ref caps, Marshal.SizeOf(caps)); + + // If the capabilities could not be retrieved. + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + // Throw an exception. + throw new OutputDeviceException(result); + } + + return caps; + } + + // Handles Windows messages. + protected virtual void HandleMessage(int handle, int msg, int instance, int param1, int param2) + { + if(msg == MOM_OPEN) + { + } + else if(msg == MOM_CLOSE) + { + } + else if(msg == MOM_DONE) + { + delegateQueue.Post(ReleaseBuffer, new IntPtr(param1)); + } + } + + // Releases buffers. + private void ReleaseBuffer(object state) + { + lock(lockObject) + { + IntPtr headerPtr = (IntPtr)state; + + // Unprepare the buffer. + int result = midiOutUnprepareHeader(Handle, headerPtr, SizeOfMidiHeader); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + Exception ex = new OutputDeviceException(result); + + OnError(new ErrorEventArgs(ex)); + } + + // Release the buffer resources. + headerBuilder.Destroy(headerPtr); + + bufferCount--; + + Monitor.Pulse(lockObject); + + Debug.Assert(bufferCount >= 0); + } + } + + public override void Dispose() + { + #region Guard + + if(IsDisposed) + { + return; + } + + #endregion + + lock(lockObject) + { + Close(); + } + } + + public override int Handle + { + get + { + return hndle; + } + } + + public static int DeviceCount + { + get + { + return midiOutGetNumDevs(); + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputStream.cs b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputStream.cs new file mode 100644 index 0000000..a0e8e15 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Device Classes/OutputDevice Classes/OutputStream.cs @@ -0,0 +1,616 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; +using System.Threading; +using Sanford.Multimedia.Timers; + +namespace Sanford.Multimedia.Midi +{ + public sealed class OutputStream : OutputDeviceBase + { + [DllImport("winmm.dll")] + private static extern int midiStreamOpen(ref int handle, ref int deviceID, int reserved, + OutputDevice.MidiOutProc proc, int instance, uint flag); + + [DllImport("winmm.dll")] + private static extern int midiStreamClose(int handle); + + [DllImport("winmm.dll")] + private static extern int midiStreamOut(int handle, IntPtr headerPtr, int sizeOfMidiHeader); + + [DllImport("winmm.dll")] + private static extern int midiStreamPause(int handle); + + [DllImport("winmm.dll")] + private static extern int midiStreamPosition(int handle, ref Time t, int sizeOfTime); + + [DllImport("winmm.dll")] + private static extern int midiStreamProperty(int handle, ref Property p, uint flags); + + [DllImport("winmm.dll")] + private static extern int midiStreamRestart(int handle); + + [DllImport("winmm.dll")] + private static extern int midiStreamStop(int handle); + + [StructLayout(LayoutKind.Sequential)] + private struct Property + { + public int sizeOfProperty; + public int property; + } + + private const uint MIDIPROP_SET = 0x80000000; + private const uint MIDIPROP_GET = 0x40000000; + private const uint MIDIPROP_TIMEDIV = 0x00000001; + private const uint MIDIPROP_TEMPO = 0x00000002; + + private const byte MEVT_CALLBACK = 0x40; + + private const byte MEVT_SHORTMSG = 0x00; + private const byte MEVT_TEMPO = 0x01; + private const byte MEVT_NOP = 0x02; + private const byte MEVT_LONGMSG = 0x80; + private const byte MEVT_COMMENT = 0x82; + private const byte MEVT_VERSION = 0x84; + + private const int MOM_POSITIONCB = 0x3CA; + + private const int SizeOfMidiEvent = 12; + + private const int EventTypeIndex = 11; + + private const int EventCodeOffset = 8; + + private MidiOutProc midiOutProc; + + private int offsetTicks = 0; + + private byte[] streamID = new byte[4]; + + private List events = new List(); + + private MidiHeaderBuilder headerBuilder = new MidiHeaderBuilder(); + + public event EventHandler NoOpOccurred; + + public OutputStream(int deviceID) : base(deviceID) + { + midiOutProc = HandleMessage; + + int result = midiStreamOpen(ref hndle, ref deviceID, 1, midiOutProc, 0, CALLBACK_FUNCTION); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + + protected override void Dispose(bool disposing) + { + if(disposing) + { + lock(lockObject) + { + Reset(); + + int result = midiStreamClose(Handle); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + else + { + midiOutReset(Handle); + midiStreamClose(Handle); + } + + base.Dispose(disposing); + } + + public override void Close() + { + #region Guard + + if(!IsDisposed) + { + return; + } + + #endregion + + Dispose(true); + } + + public void StartPlaying() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + lock(lockObject) + { + int result = midiStreamRestart(Handle); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + + public void PausePlaying() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + lock(lockObject) + { + int result = midiStreamPause(Handle); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + + public void StopPlaying() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + lock(lockObject) + { + int result = midiStreamStop(Handle); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + + public override void Reset() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + offsetTicks = 0; + events.Clear(); + + base.Reset(); + } + + public void Write(MidiEvent e) + { + switch(e.MidiMessage.MessageType) + { + case MessageType.Channel: + case MessageType.SystemCommon: + case MessageType.SystemRealtime: + Write(e.DeltaTicks, (ShortMessage)e.MidiMessage); + break; + + case MessageType.SystemExclusive: + Write(e.DeltaTicks, (SysExMessage)e.MidiMessage); + break; + + case MessageType.Meta: + Write(e.DeltaTicks, (MetaMessage)e.MidiMessage); + break; + } + } + + private void Write(int deltaTicks, ShortMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + // Delta time. + events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks)); + + // Stream ID. + events.AddRange(streamID); + + // Event code. + byte[] eventCode = message.GetBytes(); + eventCode[eventCode.Length - 1] = MEVT_SHORTMSG; + events.AddRange(eventCode); + + offsetTicks = 0; + } + + private void Write(int deltaTicks, SysExMessage message) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + // Delta time. + events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks)); + + // Stream ID. + events.AddRange(streamID); + + // Event code. + byte[] eventCode = BitConverter.GetBytes(message.Length); + eventCode[eventCode.Length - 1] = MEVT_LONGMSG; + events.AddRange(eventCode); + + byte[] sysExData; + + if(message.Length % 4 != 0) + { + sysExData = new byte[message.Length + (message.Length % 4)]; + message.GetBytes().CopyTo(sysExData, 0); + } + else + { + sysExData = message.GetBytes(); + } + + // SysEx data. + events.AddRange(sysExData); + + offsetTicks = 0; + } + + private void Write(int deltaTicks, MetaMessage message) + { + if(message.MetaType == MetaType.Tempo) + { + // Delta time. + events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks)); + + // Stream ID. + events.AddRange(streamID); + + TempoChangeBuilder builder = new TempoChangeBuilder(message); + + byte[] t = BitConverter.GetBytes(builder.Tempo); + + t[t.Length - 1] = MEVT_SHORTMSG | MEVT_TEMPO; + + // Event code. + events.AddRange(t); + + offsetTicks = 0; + } + else + { + offsetTicks += deltaTicks; + } + } + + public void WriteNoOp(int deltaTicks, int data) + { + // Delta time. + events.AddRange(BitConverter.GetBytes(deltaTicks + offsetTicks)); + + // Stream ID. + events.AddRange(streamID); + + // Event code. + byte[] eventCode = BitConverter.GetBytes(data); + eventCode[eventCode.Length - 1] = (byte)(MEVT_NOP | MEVT_CALLBACK); + events.AddRange(eventCode); + + offsetTicks = 0; + } + + public void Flush() + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + lock(lockObject) + { + headerBuilder.InitializeBuffer(events); + headerBuilder.Build(); + + events.Clear(); + + int result = midiOutPrepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader); + + if(result == MidiDeviceException.MMSYSERR_NOERROR) + { + bufferCount++; + } + else + { + headerBuilder.Destroy(); + + throw new OutputDeviceException(result); + } + + result = midiStreamOut(Handle, headerBuilder.Result, SizeOfMidiHeader); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + midiOutUnprepareHeader(Handle, headerBuilder.Result, SizeOfMidiHeader); + + headerBuilder.Destroy(); + + throw new OutputDeviceException(result); + } + } + } + + public Time GetTime(TimeType type) + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + Time t = new Time(); + + t.type = (int)type; + + lock(lockObject) + { + int result = midiStreamPosition(Handle, ref t, Marshal.SizeOf(typeof(Time))); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + + return t; + } + + private void OnNoOpOccurred(NoOpEventArgs e) + { + EventHandler handler = NoOpOccurred; + + if(handler != null) + { + handler(this, e); + } + } + + protected override void HandleMessage(int handle, int msg, int instance, int param1, int param2) + { + if(msg == MOM_POSITIONCB) + { + delegateQueue.Post(HandleNoOp, new IntPtr(param1)); + } + else + { + base.HandleMessage(handle, msg, instance, param1, param2); + } + } + + private void HandleNoOp(object state) + { + IntPtr headerPtr = (IntPtr)state; + MidiHeader header = (MidiHeader)Marshal.PtrToStructure(headerPtr, typeof(MidiHeader)); + + byte[] midiEvent = new byte[SizeOfMidiEvent]; + + for(int i = 0; i < midiEvent.Length; i++) + { + midiEvent[i] = Marshal.ReadByte(header.data, header.offset + i); + } + + // If this is a NoOp event. + if((midiEvent[EventTypeIndex] & MEVT_NOP) == MEVT_NOP) + { + // Clear the event type byte. + midiEvent[EventTypeIndex] = 0; + + NoOpEventArgs e = new NoOpEventArgs(BitConverter.ToInt32(midiEvent, EventCodeOffset)); + + context.Post(new SendOrPostCallback(delegate(object s) + { + OnNoOpOccurred(e); + }), null); + } + } + + public int Division + { + get + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + Property d = new Property(); + + d.sizeOfProperty = Marshal.SizeOf(typeof(Property)); + + lock(lockObject) + { + int result = midiStreamProperty(Handle, ref d, MIDIPROP_GET | MIDIPROP_TIMEDIV); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + + return d.property; + } + set + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + else if((value % PpqnClock.PpqnMinValue) != 0) + { + throw new ArgumentException(); + } + + #endregion + + Property d = new Property(); + + d.sizeOfProperty = Marshal.SizeOf(typeof(Property)); + d.property = value; + + lock(lockObject) + { + int result = midiStreamProperty(Handle, ref d, MIDIPROP_SET | MIDIPROP_TIMEDIV); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + } + + public int Tempo + { + get + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + + #endregion + + Property t = new Property(); + t.sizeOfProperty = Marshal.SizeOf(typeof(Property)); + + lock(lockObject) + { + int result = midiStreamProperty(Handle, ref t, MIDIPROP_GET | MIDIPROP_TEMPO); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + + return t.property; + } + set + { + #region Require + + if(IsDisposed) + { + throw new ObjectDisposedException("OutputStream"); + } + else if(value < 0) + { + throw new ArgumentOutOfRangeException("Tempo", value, + "Tempo out of range."); + } + + #endregion + + Property t = new Property(); + t.sizeOfProperty = Marshal.SizeOf(typeof(Property)); + t.property = value; + + lock(lockObject) + { + int result = midiStreamProperty(Handle, ref t, MIDIPROP_SET | MIDIPROP_TEMPO); + + if(result != MidiDeviceException.MMSYSERR_NOERROR) + { + throw new OutputDeviceException(result); + } + } + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Documentation.xml b/DependenciesCode/Sanford.Multimedia.Midi/Documentation.xml new file mode 100644 index 0000000..68eb04f --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Documentation.xml @@ -0,0 +1,2455 @@ + + + + Multimedia.Midi + + + + + Represents the base class for all MIDI devices. + + + + + Connects a MIDI InputDevice to a MIDI thru or OutputDevice, or + connects a MIDI thru device to a MIDI OutputDevice. + + + Handle to a MIDI InputDevice or a MIDI thru device (for thru + devices, this handle must belong to a MIDI OutputDevice). + + + Handle to the MIDI OutputDevice or thru device. + + + If an error occurred while connecting the two devices. + + + + + Disconnects a MIDI InputDevice from a MIDI thru or OutputDevice, or + disconnects a MIDI thru device from a MIDI OutputDevice. + + + Handle to a MIDI InputDevice or a MIDI thru device. + + + Handle to the MIDI OutputDevice to be disconnected. + + + If an error occurred while disconnecting the two devices. + + + + + Opens the MIDI device. + + + The device ID. + + + + + Closes the MIDI device. + + + + + Resets the MIDI device. + + + + + Gets the device handle. + + + + + Gets a value indicating whether the device is open. + + + + + Summary description for DeviceException. + + + + + Gets the error code that raised the exception. + + + + + Represents a MIDI device capable of receiving MIDI messages. + + + + + Represents a source of ChannelMessages. + + + + + Occurs when a ChannelMessage is received, generated, or + encountered by a IChannelSource. + + + + + Summary description for ISysExSource. + + + + + Summary description for ISysCommonSource. + + + + + Summary description for ISysRealtimeSource. + + + + + The exception that is thrown when a error occurs with the InputDevice + class. + + + + + Initializes a new instance of the InputDeviceException class with + the specified error code. + + + The error code. + + + + + Gets a message that describes the current exception. + + + + + Represents data for the InvalidShortMessageEventArgs class. + + + + + Initializes a new instance of the InvalidShortMessageEventArgs class + with the specified invalid short message. + + + The invalid short message as a packed integer. + + + + + Gets the invalid short message as a packed integer. + + + + + Represents data for the InvalidSysExMessageOccurred event. + + + + + Initializes a new instance of the InvalidSysExMessageEventArgs class + with the specified invalid system exclusive data. + + + The invalid system exclusive data. + + + + + Gets the element at the specified index. + + + + + Gets the length of the invalid system exclusive data. + + + + + Represents the Windows Multimedia MIDIHDR structure. + + + + + Builds a pointer to a MidiHeader structure. + + + + + Releases the resources associated with the built MidiHeader pointer. + + + + + Releases the resources associated with the specified MidiHeader pointer. + + + The MidiHeader pointer. + + + + + Represents Midi input device capabilities. + + + + + Manufacturer identifier of the device driver for the Midi output + device. + + + + + Product identifier of the Midi output device. + + + + + 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. + + + + + Product name. + + + + + Optional functionality supported by the device. + + + + + Represents Midi output device capabilities. + + + + + Manufacturer identifier of the device driver for the Midi output + device. + + + + + Product identifier of the Midi output device. + + + + + 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. + + + + + Product name. + + + + + Flags describing the type of the Midi output device. + + + + + Number of voices supported by an internal synthesizer device. If + the device is a port, this member is not meaningful and is set + to 0. + + + + + Maximum number of simultaneous notes that can be played by an + internal synthesizer device. If the device is a port, this member + is not meaningful and is set to 0. + + + + + Channels that an internal synthesizer device responds to, where the + least significant bit refers to channel 0 and the most significant + bit to channel 15. Port devices that transmit on all channels set + this member to 0xFFFF. + + + + + Optional functionality supported by the device. + + + + + Represents a device capable of sending MIDI messages. + + + + + Represents functionality for connecting to and disconnecting from an + IChannelSource. + + + + + Connects the IChannelSink to the specified IChannelSource. + + + The IChannelSource to which to connect. + + + + + Disconnects the IChannelSink from the specified IChannelSource. + + + The IChannelSource from which to disconnect. + + + + + Summary description for ISysExSink. + + + + + Summary description for ISysCommonSink. + + + + + Summary description for ISysRealtimeMessage. + + + + + Disposes of the OutputDevice. + + + + + Occurs when the OutputDevice has been opened. + + + + + Occurs when the OutputDevice has been closed. + + + + + Occurs when the OutputDevice has been disposed. + + + + + The exception that is thrown when a error occurs with the OutputDevice + class. + + + + + Initializes a new instance of the OutputDeviceException class with + the specified error code. + + + The error code. + + + + + Gets a message that describes the current exception. + + + + + Provides functionality for building ChannelMessages. + + + + + Represents functionality for building MIDI messages. + + + + + Builds the MIDI message. + + + + + Initializes a new instance of the ChannelMessageBuilder class. + + + + + Initializes a new instance of the ChannelMessageBuilder class with + the specified ChannelMessage. + + + The ChannelMessage to use for initializing the ChannelMessageBuilder. + + + The ChannelMessageBuilder uses the specified ChannelMessage to + initialize its property values. + + + + + Initializes the ChannelMessageBuilder with the specified + ChannelMessage. + + + The ChannelMessage to use for initializing the ChannelMessageBuilder. + + + + + Clears the ChannelMessage cache. + + + + + Builds a ChannelMessage. + + + + + Gets the number of messages in the ChannelMessage cache. + + + + + Gets the built ChannelMessage. + + + + + Gets or sets the ChannelMessage as a packed integer. + + + + + Gets or sets the Command value to use for building the + ChannelMessage. + + + + + Gets or sets the MIDI channel to use for building the + ChannelMessage. + + + MidiChannel is set to a value less than zero or greater than 15. + + + + + Gets or sets the first data value to use for building the + ChannelMessage. + + + Data1 is set to a value less than zero or greater than 127. + + + + + Gets or sets the second data value to use for building the + ChannelMessage. + + + Data2 is set to a value less than zero or greater than 127. + + + + + Provides functionality for building meta text messages. + + + + + Initializes a new instance of the MetaMessageTextBuilder class. + + + + + Initializes a new instance of the MetaMessageTextBuilder class with the + specified type. + + + The type of MetaMessage. + + + If the MetaMessage type is not a text based type. + + + The MetaMessage type must be one of the following text based + types: + + + Copyright + + + Cuepoint + + + DeviceName + + + InstrumentName + + + Lyric + + + Marker + + + ProgramName + + + Text + + + TrackName + + + If the MetaMessage is not a text based type, an exception + will be thrown. + + + + + Initializes a new instance of the MetaMessageTextBuilder class with the + specified type. + + + The type of MetaMessage. + + + If the MetaMessage type is not a text based type. + + + The MetaMessage type must be one of the following text based + types: + + + Copyright + + + Cuepoint + + + DeviceName + + + InstrumentName + + + Lyric + + + Marker + + + ProgramName + + + Text + + + TrackName + + + If the MetaMessage is not a text based type, an exception + will be thrown. + + + + + Initializes a new instance of the MetaMessageTextBuilder class with the + specified MetaMessage. + + + The MetaMessage to use for initializing the MetaMessageTextBuilder. + + + If the MetaMessage is not a text based type. + + + The MetaMessage must be one of the following text based types: + + + Copyright + + + Cuepoint + + + DeviceName + + + InstrumentName + + + Lyric + + + Marker + + + ProgramName + + + Text + + + TrackName + + + If the MetaMessage is not a text based type, an exception will be + thrown. + + + + + Initializes the MetaMessageTextBuilder with the specified MetaMessage. + + + The MetaMessage to use for initializing the MetaMessageTextBuilder. + + + If the MetaMessage is not a text based type. + + + + + Indicates whether or not the specified MetaType is a text based + type. + + + The MetaType to test. + + + true if the MetaType is a text based type; + otherwise, false. + + + + + Builds the text MetaMessage. + + + + + Gets or sets the text for the MetaMessage. + + + + + Gets or sets the MetaMessage type. + + + If the type is not a text based type. + + + + + Gets the built MetaMessage. + + + + + Provides functionality for building song position pointer messages. + + + + + Initializes a new instance of the SongPositionPointerBuilder class. + + + + + Initializes a new instance of the SongPositionPointerBuilder class + with the specified song position pointer message. + + + The song position pointer message to use for initializing the + SongPositionPointerBuilder. + + + If message is not a song position pointer message. + + + + + Initializes the SongPositionPointerBuilder with the specified + SysCommonMessage. + + + The SysCommonMessage to use to initialize the + SongPositionPointerBuilder. + + + If the SysCommonMessage is not a song position pointer message. + + + + + Builds a song position pointer message. + + + + + Gets or sets the sequence position in ticks. + + + Value is set to less than zero. + + + Note: the position in ticks value is converted to the song position + pointer value. Since the song position pointer has a lower + resolution than the position in ticks, there is a probable loss of + resolution when setting the position in ticks value. + + + + + Gets or sets the PulsesPerQuarterNote object. + + + Value is not a multiple of 24. + + + + + Gets or sets the song position. + + + Value is set to less than zero. + + + + + Gets the built song position pointer message. + + + + + Provides functionality for building SysCommonMessages. + + + + + Initializes a new instance of the SysCommonMessageBuilder class. + + + + + Initializes a new instance of the SysCommonMessageBuilder class + with the specified SystemCommonMessage. + + + The SysCommonMessage to use for initializing the + SysCommonMessageBuilder. + + + The SysCommonMessageBuilder uses the specified SysCommonMessage to + initialize its property values. + + + + + Initializes the SysCommonMessageBuilder with the specified + SysCommonMessage. + + + The SysCommonMessage to use for initializing the + SysCommonMessageBuilder. + + + + + Clears the SysCommonMessageBuilder cache. + + + + + Builds a SysCommonMessage. + + + + + Gets the number of messages in the SysCommonMessageBuilder cache. + + + + + Gets the built SysCommonMessage. + + + + + Gets or sets the SysCommonMessage as a packed integer. + + + + + Gets or sets the type of SysCommonMessage. + + + + + Gets or sets the first data value to use for building the + SysCommonMessage. + + + Data1 is set to a value less than zero or greater than 127. + + + + + Gets or sets the second data value to use for building the + SysCommonMessage. + + + Data2 is set to a value less than zero or greater than 127. + + + + + Provides functionality for building tempo messages. + + + + + Initializes a new instance of the TempoChangeBuilder class. + + + + + Initialize a new instance of the TempoChangeBuilder class with the + specified MetaMessage. + + + The MetaMessage to use for initializing the TempoChangeBuilder class. + + + If the specified MetaMessage is not a tempo type. + + + The TempoChangeBuilder uses the specified MetaMessage to initialize + its property values. + + + + + Initializes the TempoChangeBuilder with the specified MetaMessage. + + + The MetaMessage to use for initializing the TempoChangeBuilder. + + + If the specified MetaMessage is not a tempo type. + + + + + Builds the tempo change MetaMessage. + + + + + Gets or sets the Tempo object. + + + Value is set to less than zero. + + + + + Gets the built message. + + + + + Provides easy to use functionality for meta time signature messages. + + + + + Initializes a new instance of the TimeSignatureBuilder class. + + + + + Initializes a new instance of the TimeSignatureBuilder class with the + specified MetaMessage. + + + The MetaMessage to use for initializing the TimeSignatureBuilder class. + + + If the specified MetaMessage is not a time signature type. + + + The TimeSignatureBuilder uses the specified MetaMessage to + initialize its property values. + + + + + Initializes the TimeSignatureBuilder with the specified MetaMessage. + + + The MetaMessage to use for initializing the TimeSignatureBuilder. + + + If the specified MetaMessage is not a time signature type. + + + + + Builds the time signature message. + + + + + Gets or sets the numerator. + + + Numerator is set to a value less than one. + + + + + Gets or sets the denominator. + + + Denominator is set to a value less than 2. + + + Denominator is set to a value that is not a multiple of 2. + + + + + Gets or sets the clocks per metronome click. + + + Clocks per metronome click determines how many MIDI clocks occur + for each metronome click. + + + + + Gets or sets how many thirty second notes there are for each + quarter note. + + + + + Gets the built message. + + + + + Represents the method that handles ChannelMessage events. + + + + + Defines constants for ChannelMessage types. + + + + + Represents the note-off command type. + + + + + Represents the note-on command type. + + + + + Represents the poly pressure (aftertouch) command type. + + + + + Represents the controller command type. + + + + + Represents the program change command type. + + + + + Represents the channel pressure (aftertouch) command + type. + + + + + Represents the pitch wheel command type. + + + + + Defines constants for controller types. + + + + + The Bank Select coarse. + + + + + The Modulation Wheel coarse. + + + + + The Breath Control coarse. + + + + + The Foot Pedal coarse. + + + + + The Portamento Time coarse. + + + + + The Data Entry Slider coarse. + + + + + The Volume coarse. + + + + + The Balance coarse. + + + + + The Pan position coarse. + + + + + The Expression coarse. + + + + + The Effect Control 1 coarse. + + + + + The Effect Control 2 coarse. + + + + + The General Puprose Slider 1 + + + + + The General Puprose Slider 2 + + + + + The General Puprose Slider 3 + + + + + The General Puprose Slider 4 + + + + + The Bank Select fine. + + + + + The Modulation Wheel fine. + + + + + The Breath Control fine. + + + + + The Foot Pedal fine. + + + + + The Portamento Time fine. + + + + + The Data Entry Slider fine. + + + + + The Volume fine. + + + + + The Balance fine. + + + + + The Pan position fine. + + + + + The Expression fine. + + + + + The Effect Control 1 fine. + + + + + The Effect Control 2 fine. + + + + + The Hold Pedal 1. + + + + + The Portamento. + + + + + The Sustenuto Pedal. + + + + + The Soft Pedal. + + + + + The Legato Pedal. + + + + + The Hold Pedal 2. + + + + + The Sound Variation. + + + + + The Sound Timbre. + + + + + The Sound Release Time. + + + + + The Sound Attack Time. + + + + + The Sound Brightness. + + + + + The Sound Control 6. + + + + + The Sound Control 7. + + + + + The Sound Control 8. + + + + + The Sound Control 9. + + + + + The Sound Control 10. + + + + + The General Purpose Button 1. + + + + + The General Purpose Button 2. + + + + + The General Purpose Button 3. + + + + + The General Purpose Button 4. + + + + + The Effects Level. + + + + + The Tremelo Level. + + + + + The Chorus Level. + + + + + The Celeste Level. + + + + + The Phaser Level. + + + + + The Data Button Increment. + + + + + The Data Button Decrement. + + + + + The NonRegistered Parameter Fine. + + + + + The NonRegistered Parameter Coarse. + + + + + The Registered Parameter Fine. + + + + + The Registered Parameter Coarse. + + + + + The All Sound Off. + + + + + The All Controllers Off. + + + + + The Local Keyboard. + + + + + The All Notes Off. + + + + + The Omni Mode Off. + + + + + The Omni Mode On. + + + + + The Mono Operation. + + + + + The Poly Operation. + + + + + Represents MIDI channel messages. + + + + + Represents the basic class for all MIDI short messages. + + + MIDI short messages represent all MIDI messages except meta messages + and system exclusive messages. This includes channel messages, system + realtime messages, and system common messages. + + + + + Represents the basic functionality for all MIDI messages. + + + + + Gets the MIDI message's status value. + + + + + Gets the MIDI message's type. + + + + + Gets the MIDI message's status value. + + + + + Gets the MessageType. + + + + + Gets the short message as a packed integer. + + + The message is packed into an integer value with the low-order byte + of the low-word representing the status value. The high-order byte + of the low-word represents the first data value, and the low-order + byte of the high-word represents the second data value. + + + + + Maximum value allowed for MIDI channels. + + + + + Initializes a new instance of the ChannelMessage class with the + specified command, MIDI channel, and data 1 values. + + + The command value. + + + The MIDI channel. + + + The data 1 value. + + + If midiChannel is less than zero or greater than 15. Or if + data1 is less than zero or greater than 127. + + + + + Initializes a new instance of the ChannelMessage class with the + specified command, MIDI channel, data 1, and data 2 values. + + + The command value. + + + The MIDI channel. + + + The data 1 value. + + + The data 2 value. + + + If midiChannel is less than zero or greater than 15. Or if + data1 or data 2 is less than zero or greater than 127. + + + + + Returns a value for the current ChannelMessage suitable for use in + hashing algorithms. + + + A hash code for the current ChannelMessage. + + + + + Determines whether two ChannelMessage instances are equal. + + + The ChannelMessage to compare with the current ChannelMessage. + + + true if the specified ChannelMessage is equal to the current + ChannelMessage; otherwise, false. + + + + + Returns a value indicating how many bytes are used for the + specified ChannelCommand. + + + The ChannelCommand value to test. + + + The number of bytes used for the specified ChannelCommand. + + + + + Unpacks the command value from the specified integer channel + message. + + + The message to unpack. + + + The command value for the packed message. + + + + + Unpacks the MIDI channel from the specified integer channel + message. + + + The message to unpack. + + + The MIDI channel for the pack message. + + + + + Packs the MIDI channel into the specified integer message. + + + The message into which the MIDI channel is packed. + + + The MIDI channel to pack into the message. + + + An integer message. + + + If midiChannel is less than zero or greater than 15. + + + + + Packs the command value into an integer message. + + + The message into which the command is packed. + + + The command value to pack into the message. + + + An integer message. + + + + + Gets the channel command value. + + + + + Gets the MIDI channel. + + + + + Gets the first data value. + + + + + Gets the second data value. + + + + + Gets the ChannelMessage's status value. + + + + + Gets the ChannelMessage as a packed integer. + + + + + Gets the ChanngelMessage's MessageType. + + + + + Provides data for ChannelMessage events. + + + + + Initializes a new instance of the ChannelMessageEventArgs class with the + specified ChannelMessage and time stamp. + + + The ChannelMessage for this event. + + + + + Gets the ChannelMessage for this event. + + + + + Represents constant values for MIDI message types. + + + + + Represents MetaMessage types. + + + + + Represents sequencer number type. + + + + + Represents the text type. + + + + + Represents the copyright type. + + + + + Represents the track name type. + + + + + Represents the instrument name type. + + + + + Represents the lyric type. + + + + + Represents the marker type. + + + + + Represents the cue point type. + + + + + Represents the program name type. + + + + + Represents the device name type. + + + + + Represents then end of track type. + + + + + Represents the tempo type. + + + + + Represents the Smpte offset type. + + + + + Represents the time signature type. + + + + + Represents the key signature type. + + + + + Represents the proprietary event type. + + + + + 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. + + + + + The amount to shift data bytes when calculating the hash code. + + + + + Length in bytes for tempo meta message data. + + + + + Length in bytes for SMPTE offset meta message data. + + + + + Length in bytes for time signature meta message data. + + + + + Length in bytes for key signature meta message data. + + + + + End of track meta message. + + + + + 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. + + + + + Gets a copy of the data bytes for this meta message. + + + A copy of the data bytes for this meta message. + + + + + Returns a value for the current MetaMessage suitable for use in + hashing algorithms. + + + A hash code for the current MetaMessage. + + + + + 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. + + + + + 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. + + + + + Gets the element at the specified index. + + + index is less than zero or greater than or equal to Length. + + + + + Gets the length of the meta message. + + + + + Gets the type of meta message. + + + + + Gets the status value. + + + + + Gets the MetaMessage's MessageType. + + + + + Provides data for MetaMessage events. + + + + + Initializes a new instance of the MetaMessageEventArgs class with the + specified MetaMessage. + + + The MetaMessage for this event. + + + + + Gets the MetaMessage for this event. + + + + + Represents the method that handles SysCommonMessage events. + + + + + Defines constants representing the various system common message types. + + + + + Represents the MTC system common message type. + + + + + Represents the song position pointer type. + + + + + Represents the song select type. + + + + + Represents the tune request type. + + + + + Represents MIDI system common messages. + + + + + Initializes a new instance of the SysCommonMessage class with the + specified type. + + + The type of SysCommonMessage. + + + + + Initializes a new instance of the SysCommonMessage class with the + specified type and the first data value. + + + The type of SysCommonMessage. + + + The first data value. + + + If data1 is less than zero or greater than 127. + + + + + Initializes a new instance of the SysCommonMessage class with the + specified type, first data value, and second data value. + + + The type of SysCommonMessage. + + + The first data value. + + + The second data value. + + + If data1 or data2 is less than zero or greater than 127. + + + + + Returns a value for the current SysCommonMessage suitable for use + in hashing algorithms. + + + A hash code for the current SysCommonMessage. + + + + + Determines whether two SysCommonMessage instances are equal. + + + The SysCommonMessage to compare with the current SysCommonMessage. + + + true if the specified SysCommonMessage is equal to the + current SysCommonMessage; otherwise, false. + + + + + Gets the SysCommonType. + + + + + Gets the first data value. + + + + + Gets the second data value. + + + + + Gets the status value. + + + + + Gets the SysCommonMessage as a packed integer. + + + + + Gets the MessageType. + + + + + Represents data for SysCommonMessage events. + + + + + Initializes a new instance of the SysCommonMessageEventArgs class with the + specified SysCommonMessage. + + + The SysCommonMessage for this event. + + + + + Gets the SysCommonMessage for this event. + + + + + Defines constants representing various system exclusive message types. + + + + + Represents the start of system exclusive message type. + + + + + Represents the continuation of a system exclusive message. + + + + + Represents the method that handles SysExMessage events. + + + + + Represents MIDI system exclusive messages. + + + + + Maximum value for system exclusive channels. + + + + + Initializes a new instance of the SysExMessage class with the + specified system exclusive data. + + + The system exclusive data. + + + The system exclusive data's status byte, the first byte in the + data, must have a value of 0xF0 or 0xF7. + + + + + Gets the element at the specified index. + + + If index is less than zero or greater than or equal to the length + of the message. + + + + + Gets the length of the system exclusive data. + + + + + Gets the system exclusive type. + + + + + Gets the status value. + + + + + Gets the MessageType. + + + + + Provides data for SysExMessage events. + + + + + Initializes a new instance of the SysExMessageEventArgs class with the + specified system exclusive message and the time stamp. + + + The SysExMessage for this event. + + + + + Gets the system exclusive message for this event. + + + + + Represents the method that handles SysRealtimeMessage events. + + + + + Defines constants representing the various system realtime message types. + + + + + Represents the clock system realtime type. + + + + + Represents the tick system realtime type. + + + + + Represents the start system realtime type. + + + + + Represents the continue system realtime type. + + + + + Represents the stop system realtime type. + + + + + Represents the active sense system realtime type. + + + + + Represents the reset system realtime type. + + + + + Represents MIDI system realtime messages. + + + System realtime messages are MIDI messages that are primarily concerned + with controlling and synchronizing MIDI devices. + + + + + The instance of the system realtime start message. + + + + + The instance of the system realtime continue message. + + + + + The instance of the system realtime stop message. + + + + + The instance of the system realtime clock message. + + + + + The instance of the system realtime tick message. + + + + + The instance of the system realtime active sense message. + + + + + The instance of the system realtime reset message. + + + + + Returns a value for the current SysRealtimeMessage suitable for use in + hashing algorithms. + + + A hash code for the current SysRealtimeMessage. + + + + + Determines whether two SysRealtimeMessage instances are equal. + + + The SysRealtimeMessage to compare with the current SysRealtimeMessage. + + + true if the specified SysRealtimeMessage is equal to the current + SysRealtimeMessage; otherwise, false. + + + + + Gets the SysRealtimeType. + + + + + Gets the status value. + + + + + Gets the system realtime message as a packed integer. + + + + + Gets the MessageType. + + + + + Represents data for SysRealtimeMessage events. + + + + + Initializes a new instance of the SysRealtimeMessageEventArgs class with + the specified SysRealtimeMessage. + + + The SysRealtimeMessage for this event. + + + + + Gets the SysRealtimeMessage for this event. + + + + + Summary description for ChannelChaser. + + + + + Summary description for ChannelCleaner. + + + + + Summary description for ChannelStopper. + + + + + Represents functionality for connecting to and disconnecting from an + IMetaSource. + + + + + Connects the IMetaSink to the specified IMetaSource. + + + The IMetaSource to which to connect. + + + + + Disconnects the IMetaSink from the specified IMetaSource. + + + The IMetaSource from which to disconnect. + + + + + Represents a source of MetaMessages. + + + + + Occurs when a MetaMessage is received, generated, or + encountered by a IMetaSource. + + + + + Represents functionality for connecting to and disconnecting from an + IMidiEventSource. + + + + + Connects the IMidiEventSink to the specified IMidiEventSource. + + + The IMidiEventSource to which to connect. + + + + + Disconnects the IMidiEventSink from the specified IMidiEventSource. + + + The IMidiEventSource from which to disconnect. + + + + + Represents a source of MidiEvents. + + + + + Occurs when a MidiEvent is received, generated, or + encountered by a IMidiEventSource. + + + + + Summary description for MidiEventCollection. + + + + + Removes any orphaned note/pedal-on or note/pedal-off messages + from the Track. + + + + + Moves a MidiEvent by the specified amount. + + + The index of the MidiEvent to move. + + + The number of ticks to move the MidiEvent. + + + + + Merges the specified Track with the current Track. + + + The Track to merge with. + + + + + Summary description for Track. + + + + + Initializes a new instance of the Track class. + + + + + Summary description for TrackReader. + + + + + Summary description for TrackWriter. + + + + + Summary description for IClock. + + + + + Represents the method for handling Midi events. + + + + + Represents a time-stamped MIDI event. + + + + + Initializes a new instance of the Midi event struct with the + specified Midi message and the number of ticks for this event. + + + The Midi message for the event. + + + The delta tick value for the event. + + + + + Gets the Midi message for the Midi event. + + + + + Gets or sets the ticks for the Midi event. + + + Thrown if the ticks value is set to a negative number. + + + + + Provides data for Midi events. + + + + + Initializes a new instance of the MidiEventArgs class with the + specified Midi event. + + + The Midi event for this event. + + + + + Gets the Midi event for this event. + + + + + Summary description for MidiFileProperties. + + + + + Summary description for MidiInternalClock. + + + + + Summary description for PpqnClock. + + + + + Summary description for Sequence. + + + + + Defines constants representing the General MIDI instrument set. + + + + + Converts a Midi note number to its corresponding frequency. + + + + + Converts note to frequency. + + + The number of the note to convert. + + + The frequency of the specified note. + + + + diff --git a/DependenciesCode/Sanford.Multimedia.Midi/GeneralMidi.cs b/DependenciesCode/Sanford.Multimedia.Midi/GeneralMidi.cs new file mode 100644 index 0000000..b5da337 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/GeneralMidi.cs @@ -0,0 +1,171 @@ +#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 + +namespace Sanford.Multimedia.Midi +{ + /// + /// Defines constants representing the General MIDI instrument set. + /// + public enum GeneralMidiInstrument + { + AcousticGrandPiano, + BrightAcousticPiano, + ElectricGrandPiano, + HonkyTonkPiano, + ElectricPiano1, + ElectricPiano2, + Harpsichord, + Clavinet, + Celesta, + Glockenspiel, + MusicBox, + Vibraphone, + Marimba, + Xylophone, + TubularBells, + Dulcimer, + DrawbarOrgan, + PercussiveOrgan, + RockOrgan, + ChurchOrgan, + ReedOrgan, + Accordion, + Harmonica, + TangoAccordion, + AcousticGuitarNylon, + AcousticGuitarSteel, + ElectricGuitarJazz, + ElectricGuitarClean, + ElectricGuitarMuted, + OverdrivenGuitar, + DistortionGuitar, + GuitarHarmonics, + AcousticBass, + ElectricBassFinger, + ElectricBassPick, + FretlessBass, + SlapBass1, + SlapBass2, + SynthBass1, + SynthBass2, + Violin, + Viola, + Cello, + Contrabass, + TremoloStrings, + PizzicatoStrings, + OrchestralHarp, + Timpani, + StringEnsemble1, + StringEnsemble2, + SynthStrings1, + SynthStrings2, + ChoirAahs, + VoiceOohs, + SynthVoice, + OrchestraHit, + Trumpet, + Trombone, + Tuba, + MutedTrumpet, + FrenchHorn, + BrassSection, + SynthBrass1, + SynthBrass2, + SopranoSax, + AltoSax, + TenorSax, + BaritoneSax, + Oboe, + EnglishHorn, + Bassoon, + Clarinet, + Piccolo, + Flute, + Recorder, + PanFlute, + BlownBottle, + Shakuhachi, + Whistle, + Ocarina, + Lead1Square, + Lead2Sawtooth, + Lead3Calliope, + Lead4Chiff, + Lead5Charang, + Lead6Voice, + Lead7Fifths, + Lead8BassAndLead, + Pad1NewAge, + Pad2Warm, + Pad3Polysynth, + Pad4Choir, + Pad5Bowed, + Pad6Metallic, + Pad7Halo, + Pad8Sweep, + Fx1Rain, + Fx2Soundtrack, + Fx3Crystal, + Fx4Atmosphere, + Fx5Brightness, + Fx6Goblins, + Fx7Echoes, + Fx8SciFi, + Sitar, + Banjo, + Shamisen, + Koto, + Kalimba, + BagPipe, + Fiddle, + Shanai, + TinkleBell, + Agogo, + SteelDrums, + Woodblock, + TaikoDrum, + MelodicTom, + SynthDrum, + ReverseCymbal, + GuitarFretNoise, + BreathNoise, + Seashore, + BirdTweet, + TelephoneRing, + Helicopter, + Applause, + Gunshot + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/ChannelMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/ChannelMessage.cs new file mode 100644 index 0000000..09634f6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/ChannelMessage.cs @@ -0,0 +1,746 @@ +#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 Channel Command Types + + /// + /// Defines constants for ChannelMessage types. + /// + public enum ChannelCommand + { + /// + /// Represents the note-off command type. + /// + NoteOff = 0x80, + + /// + /// Represents the note-on command type. + /// + NoteOn = 0x90, + + /// + /// Represents the poly pressure (aftertouch) command type. + /// + PolyPressure = 0xA0, + + /// + /// Represents the controller command type. + /// + Controller = 0xB0, + + /// + /// Represents the program change command type. + /// + ProgramChange = 0xC0, + + /// + /// Represents the channel pressure (aftertouch) command + /// type. + /// + ChannelPressure = 0xD0, + + /// + /// Represents the pitch wheel command type. + /// + PitchWheel = 0xE0 + } + + #endregion + + #region Controller Types + + /// + /// Defines constants for controller types. + /// + public enum ControllerType + { + /// + /// The Bank Select coarse. + /// + BankSelect, + + /// + /// The Modulation Wheel coarse. + /// + ModulationWheel, + + /// + /// The Breath Control coarse. + /// + BreathControl, + + /// + /// The Foot Pedal coarse. + /// + FootPedal = 4, + + /// + /// The Portamento Time coarse. + /// + PortamentoTime, + + /// + /// The Data Entry Slider coarse. + /// + DataEntrySlider, + + /// + /// The Volume coarse. + /// + Volume, + + /// + /// The Balance coarse. + /// + Balance, + + /// + /// The Pan position coarse. + /// + Pan = 10, + + /// + /// The Expression coarse. + /// + Expression, + + /// + /// The Effect Control 1 coarse. + /// + EffectControl1, + + /// + /// The Effect Control 2 coarse. + /// + EffectControl2, + + /// + /// The General Puprose Slider 1 + /// + GeneralPurposeSlider1 = 16, + + /// + /// The General Puprose Slider 2 + /// + GeneralPurposeSlider2, + + /// + /// The General Puprose Slider 3 + /// + GeneralPurposeSlider3, + + /// + /// The General Puprose Slider 4 + /// + GeneralPurposeSlider4, + + /// + /// The Bank Select fine. + /// + BankSelectFine = 32, + + /// + /// The Modulation Wheel fine. + /// + ModulationWheelFine, + + /// + /// The Breath Control fine. + /// + BreathControlFine, + + /// + /// The Foot Pedal fine. + /// + FootPedalFine = 36, + + /// + /// The Portamento Time fine. + /// + PortamentoTimeFine, + + /// + /// The Data Entry Slider fine. + /// + DataEntrySliderFine, + + /// + /// The Volume fine. + /// + VolumeFine, + + /// + /// The Balance fine. + /// + BalanceFine, + + /// + /// The Pan position fine. + /// + PanFine = 42, + + /// + /// The Expression fine. + /// + ExpressionFine, + + /// + /// The Effect Control 1 fine. + /// + EffectControl1Fine, + + /// + /// The Effect Control 2 fine. + /// + EffectControl2Fine, + + /// + /// The Hold Pedal 1. + /// + HoldPedal1 = 64, + + /// + /// The Portamento. + /// + Portamento, + + /// + /// The Sustenuto Pedal. + /// + SustenutoPedal, + + /// + /// The Soft Pedal. + /// + SoftPedal, + + /// + /// The Legato Pedal. + /// + LegatoPedal, + + /// + /// The Hold Pedal 2. + /// + HoldPedal2, + + /// + /// The Sound Variation. + /// + SoundVariation, + + /// + /// The Sound Timbre. + /// + SoundTimbre, + + /// + /// The Sound Release Time. + /// + SoundReleaseTime, + + /// + /// The Sound Attack Time. + /// + SoundAttackTime, + + /// + /// The Sound Brightness. + /// + SoundBrightness, + + /// + /// The Sound Control 6. + /// + SoundControl6, + + /// + /// The Sound Control 7. + /// + SoundControl7, + + /// + /// The Sound Control 8. + /// + SoundControl8, + + /// + /// The Sound Control 9. + /// + SoundControl9, + + /// + /// The Sound Control 10. + /// + SoundControl10, + + /// + /// The General Purpose Button 1. + /// + GeneralPurposeButton1, + + /// + /// The General Purpose Button 2. + /// + GeneralPurposeButton2, + + /// + /// The General Purpose Button 3. + /// + GeneralPurposeButton3, + + /// + /// The General Purpose Button 4. + /// + GeneralPurposeButton4, + + /// + /// The Effects Level. + /// + EffectsLevel = 91, + + /// + /// The Tremelo Level. + /// + TremeloLevel, + + /// + /// The Chorus Level. + /// + ChorusLevel, + + /// + /// The Celeste Level. + /// + CelesteLevel, + + /// + /// The Phaser Level. + /// + PhaserLevel, + + /// + /// The Data Button Increment. + /// + DataButtonIncrement, + + /// + /// The Data Button Decrement. + /// + DataButtonDecrement, + + /// + /// The NonRegistered Parameter Fine. + /// + NonRegisteredParameterFine, + + /// + /// The NonRegistered Parameter Coarse. + /// + NonRegisteredParameterCoarse, + + /// + /// The Registered Parameter Fine. + /// + RegisteredParameterFine, + + /// + /// The Registered Parameter Coarse. + /// + RegisteredParameterCoarse, + + /// + /// The All Sound Off. + /// + AllSoundOff = 120, + + /// + /// The All Controllers Off. + /// + AllControllersOff, + + /// + /// The Local Keyboard. + /// + LocalKeyboard, + + /// + /// The All Notes Off. + /// + AllNotesOff, + + /// + /// The Omni Mode Off. + /// + OmniModeOff, + + /// + /// The Omni Mode On. + /// + OmniModeOn, + + /// + /// The Mono Operation. + /// + MonoOperation, + + /// + /// The Poly Operation. + /// + PolyOperation + } + + #endregion + + /// + /// Represents MIDI channel messages. + /// + [ImmutableObject(true)] + public sealed class ChannelMessage : ShortMessage + { + #region ChannelEventArgs Members + + #region Constants + + // + // Bit manipulation constants. + // + + private const int MidiChannelMask = ~15; + private const int CommandMask = ~240; + + /// + /// Maximum value allowed for MIDI channels. + /// + public const int MidiChannelMaxValue = 15; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the ChannelEventArgs class with the + /// specified command, MIDI channel, and data 1 values. + /// + /// + /// The command value. + /// + /// + /// The MIDI channel. + /// + /// + /// The data 1 value. + /// + /// + /// If midiChannel is less than zero or greater than 15. Or if + /// data1 is less than zero or greater than 127. + /// + public ChannelMessage(ChannelCommand command, int midiChannel, int data1) + { + msg = 0; + + msg = PackCommand(msg, command); + msg = PackMidiChannel(msg, midiChannel); + msg = PackData1(msg, data1); + + #region Ensure + + Debug.Assert(Command == command); + Debug.Assert(MidiChannel == midiChannel); + Debug.Assert(Data1 == data1); + + #endregion + } + + /// + /// Initializes a new instance of the ChannelEventArgs class with the + /// specified command, MIDI channel, data 1, and data 2 values. + /// + /// + /// The command value. + /// + /// + /// The MIDI channel. + /// + /// + /// The data 1 value. + /// + /// + /// The data 2 value. + /// + /// + /// If midiChannel is less than zero or greater than 15. Or if + /// data1 or data 2 is less than zero or greater than 127. + /// + public ChannelMessage(ChannelCommand command, int midiChannel, + int data1, int data2) + { + msg = 0; + + msg = PackCommand(msg, command); + msg = PackMidiChannel(msg, midiChannel); + msg = PackData1(msg, data1); + msg = PackData2(msg, data2); + + #region Ensure + + Debug.Assert(Command == command); + Debug.Assert(MidiChannel == midiChannel); + Debug.Assert(Data1 == data1); + Debug.Assert(Data2 == data2); + + #endregion + } + + internal ChannelMessage(int message) + { + this.msg = message; + } + + #endregion + + #region Methods + + /// + /// Returns a value for the current ChannelEventArgs suitable for use in + /// hashing algorithms. + /// + /// + /// A hash code for the current ChannelEventArgs. + /// + public override int GetHashCode() + { + return msg; + } + + /// + /// Determines whether two ChannelEventArgs instances are equal. + /// + /// + /// The ChannelMessageEventArgs to compare with the current ChannelEventArgs. + /// + /// + /// true if the specified object is equal to the current + /// ChannelMessageEventArgs; otherwise, false. + /// + public override bool Equals(object obj) + { + #region Guard + + if(!(obj is ChannelMessage)) + { + return false; + } + + #endregion + + ChannelMessage e = (ChannelMessage)obj; + + return this.msg == e.msg; + } + + /// + /// Returns a value indicating how many bytes are used for the + /// specified ChannelCommand. + /// + /// + /// The ChannelCommand value to test. + /// + /// + /// The number of bytes used for the specified ChannelCommand. + /// + internal static int DataBytesPerType(ChannelCommand command) + { + int result; + + if(command == ChannelCommand.ChannelPressure || + command == ChannelCommand.ProgramChange) + { + result = 1; + } + else + { + result = 2; + } + + return result; + } + + /// + /// Unpacks the command value from the specified integer channel + /// message. + /// + /// + /// The message to unpack. + /// + /// + /// The command value for the packed message. + /// + internal static ChannelCommand UnpackCommand(int message) + { + return (ChannelCommand)(message & DataMask & MidiChannelMask); + } + + /// + /// Unpacks the MIDI channel from the specified integer channel + /// message. + /// + /// + /// The message to unpack. + /// + /// + /// The MIDI channel for the pack message. + /// + internal static int UnpackMidiChannel(int message) + { + return message & DataMask & CommandMask; + } + + /// + /// Packs the MIDI channel into the specified integer message. + /// + /// + /// The message into which the MIDI channel is packed. + /// + /// + /// The MIDI channel to pack into the message. + /// + /// + /// An integer message. + /// + /// + /// If midiChannel is less than zero or greater than 15. + /// + internal static int PackMidiChannel(int message, int midiChannel) + { + #region Preconditons + + if(midiChannel < 0 || midiChannel > MidiChannelMaxValue) + { + throw new ArgumentOutOfRangeException("midiChannel", midiChannel, + "MIDI channel out of range."); + } + + #endregion + + return (message & MidiChannelMask) | midiChannel; + } + + /// + /// Packs the command value into an integer message. + /// + /// + /// The message into which the command is packed. + /// + /// + /// The command value to pack into the message. + /// + /// + /// An integer message. + /// + internal static int PackCommand(int message, ChannelCommand command) + { + return (message & CommandMask) | (int)command; + } + + #endregion + + #region Properties + + /// + /// Gets the channel command value. + /// + public ChannelCommand Command + { + get + { + return UnpackCommand(msg); + } + } + + /// + /// Gets the MIDI channel. + /// + public int MidiChannel + { + get + { + return UnpackMidiChannel(msg); + } + } + + /// + /// Gets the first data value. + /// + public int Data1 + { + get + { + return UnpackData1(msg); + } + } + + /// + /// Gets the second data value. + /// + public int Data2 + { + get + { + return UnpackData2(msg); + } + } + + /// + /// Gets the EventType. + /// + public override MessageType MessageType + { + get + { + return MessageType.Channel; + } + } + + #endregion + + #endregion + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/ChannelMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/ChannelMessageEventArgs.cs new file mode 100644 index 0000000..af247f1 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/ChannelMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class ChannelMessageEventArgs : EventArgs + { + private ChannelMessage message; + + public ChannelMessageEventArgs(ChannelMessage message) + { + this.message = message; + } + + public ChannelMessage Message + { + get + { + return message; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/InvalidShortMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/InvalidShortMessageEventArgs.cs new file mode 100644 index 0000000..ccede1c --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/InvalidShortMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class InvalidShortMessageEventArgs : EventArgs + { + private int message; + + public InvalidShortMessageEventArgs(int message) + { + this.message = message; + } + + public int Message + { + get + { + return message; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/InvalidSysExMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/InvalidSysExMessageEventArgs.cs new file mode 100644 index 0000000..11b14a7 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/InvalidSysExMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class InvalidSysExMessageEventArgs : EventArgs + { + private byte[] messageData; + + public InvalidSysExMessageEventArgs(byte[] messageData) + { + this.messageData = messageData; + } + + public ICollection MessageData + { + get + { + return messageData; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/MetaMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/MetaMessageEventArgs.cs new file mode 100644 index 0000000..92e892d --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/MetaMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class MetaMessageEventArgs : EventArgs + { + private MetaMessage message; + + public MetaMessageEventArgs(MetaMessage message) + { + this.message = message; + } + + public MetaMessage Message + { + get + { + return message; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysCommonMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysCommonMessageEventArgs.cs new file mode 100644 index 0000000..ad76d6a --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysCommonMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class SysCommonMessageEventArgs : EventArgs + { + private SysCommonMessage message; + + public SysCommonMessageEventArgs(SysCommonMessage message) + { + this.message = message; + } + + public SysCommonMessage Message + { + get + { + return message; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysExMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysExMessageEventArgs.cs new file mode 100644 index 0000000..5438032 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysExMessageEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class SysExMessageEventArgs : EventArgs + { + private SysExMessage message; + + public SysExMessageEventArgs(SysExMessage message) + { + this.message = message; + } + + public SysExMessage Message + { + get + { + return message; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysRealtimeMessageEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysRealtimeMessageEventArgs.cs new file mode 100644 index 0000000..32ef726 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/EventArgs/SysRealtimeMessageEventArgs.cs @@ -0,0 +1,38 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class SysRealtimeMessageEventArgs : EventArgs + { + public static readonly SysRealtimeMessageEventArgs Start = new SysRealtimeMessageEventArgs(SysRealtimeMessage.StartMessage); + + public static readonly SysRealtimeMessageEventArgs Continue = new SysRealtimeMessageEventArgs(SysRealtimeMessage.ContinueMessage); + + public static readonly SysRealtimeMessageEventArgs Stop = new SysRealtimeMessageEventArgs(SysRealtimeMessage.StopMessage); + + public static readonly SysRealtimeMessageEventArgs Clock = new SysRealtimeMessageEventArgs(SysRealtimeMessage.ClockMessage); + + public static readonly SysRealtimeMessageEventArgs Tick = new SysRealtimeMessageEventArgs(SysRealtimeMessage.TickMessage); + + public static readonly SysRealtimeMessageEventArgs ActiveSense = new SysRealtimeMessageEventArgs(SysRealtimeMessage.ActiveSenseMessage); + + public static readonly SysRealtimeMessageEventArgs Reset = new SysRealtimeMessageEventArgs(SysRealtimeMessage.ResetMessage); + + private SysRealtimeMessage message; + + private SysRealtimeMessageEventArgs(SysRealtimeMessage message) + { + this.message = message; + } + + public SysRealtimeMessage Message + { + get + { + return message; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/IMidiMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/IMidiMessage.cs new file mode 100644 index 0000000..d0f03f0 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/IMidiMessage.cs @@ -0,0 +1,84 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Defines constants representing MIDI message types. + /// + public enum MessageType + { + Channel, + + SystemExclusive, + + SystemCommon, + + SystemRealtime, + + Meta + } + + /// + /// Represents the basic functionality for all MIDI messages. + /// + public interface IMidiMessage + { + /// + /// Gets a byte array representation of the MIDI message. + /// + /// + /// A byte array representation of the MIDI message. + /// + byte[] GetBytes(); + + /// + /// Gets the MIDI message's status value. + /// + int Status + { + get; + } + + /// + /// Gets the MIDI event's type. + /// + MessageType MessageType + { + get; + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/ChannelMessageBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/ChannelMessageBuilder.cs new file mode 100644 index 0000000..086a923 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/ChannelMessageBuilder.cs @@ -0,0 +1,256 @@ +#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.Collections; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides functionality for building ChannelMessages. + /// + public class ChannelMessageBuilder : IMessageBuilder + { + #region ChannelMessageBuilder Members + + #region Class Fields + + // Stores the ChannelMessages. + private static Hashtable messageCache = Hashtable.Synchronized(new Hashtable()); + + #endregion + + #region Fields + + // The channel message as a packed integer. + private int message = 0; + + // The built ChannelMessage + private ChannelMessage result = null; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the ChannelMessageBuilder class. + /// + public ChannelMessageBuilder() + { + Command = ChannelCommand.Controller; + MidiChannel = 0; + Data1 = (int)ControllerType.AllSoundOff; + Data2 = 0; + } + + /// + /// Initializes a new instance of the ChannelMessageBuilder class with + /// the specified ChannelMessageEventArgs. + /// + /// + /// The ChannelMessageEventArgs to use for initializing the ChannelMessageBuilder. + /// + /// + /// The ChannelMessageBuilder uses the specified ChannelMessageEventArgs to + /// initialize its property values. + /// + public ChannelMessageBuilder(ChannelMessage message) + { + Initialize(message); + } + + #endregion + + #region Methods + + /// + /// Initializes the ChannelMessageBuilder with the specified + /// ChannelMessageEventArgs. + /// + /// + /// The ChannelMessageEventArgs to use for initializing the ChannelMessageBuilder. + /// + public void Initialize(ChannelMessage message) + { + this.message = message.Message; + } + + /// + /// Clears the ChannelMessageEventArgs cache. + /// + public static void Clear() + { + messageCache.Clear(); + } + + #endregion + + #region Properties + + /// + /// Gets the number of messages in the ChannelMessageEventArgs cache. + /// + public static int Count + { + get + { + return messageCache.Count; + } + } + + /// + /// Gets the built ChannelMessageEventArgs. + /// + public ChannelMessage Result + { + get + { + return result; + } + } + + /// + /// Gets or sets the ChannelMessageEventArgs as a packed integer. + /// + internal int Message + { + get + { + return message; + } + set + { + message = value; + } + } + + /// + /// Gets or sets the Command value to use for building the + /// ChannelMessageEventArgs. + /// + public ChannelCommand Command + { + get + { + return ChannelMessage.UnpackCommand(message); + } + set + { + message = ChannelMessage.PackCommand(message, value); + } + } + + /// + /// Gets or sets the MIDI channel to use for building the + /// ChannelMessageEventArgs. + /// + /// + /// MidiChannel is set to a value less than zero or greater than 15. + /// + public int MidiChannel + { + get + { + return ChannelMessage.UnpackMidiChannel(message); + } + set + { + message = ChannelMessage.PackMidiChannel(message, value); + } + } + + /// + /// Gets or sets the first data value to use for building the + /// ChannelMessageEventArgs. + /// + /// + /// Data1 is set to a value less than zero or greater than 127. + /// + public int Data1 + { + get + { + return ShortMessage.UnpackData1(message); + } + set + { + message = ShortMessage.PackData1(message, value); + } + } + + /// + /// Gets or sets the second data value to use for building the + /// ChannelMessageEventArgs. + /// + /// + /// Data2 is set to a value less than zero or greater than 127. + /// + public int Data2 + { + get + { + return ShortMessage.UnpackData2(message); + } + set + { + message = ShortMessage.PackData2(message, value); + } + } + + #endregion + + #endregion + + #region IMessageBuilder Members + + /// + /// Builds a ChannelMessageEventArgs. + /// + public void Build() + { + result = (ChannelMessage)messageCache[message]; + + // If the message does not exist. + if(result == null) + { + result = new ChannelMessage(message); + + // Add message to cache. + messageCache.Add(message, result); + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/IMessageBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/IMessageBuilder.cs new file mode 100644 index 0000000..ae75c49 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/IMessageBuilder.cs @@ -0,0 +1,51 @@ +#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 + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents functionality for building MIDI messages. + /// + public interface IMessageBuilder + { + #region IMessageBuilder Members + + /// + /// Builds the MIDI message. + /// + void Build(); + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/KeySignatureBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/KeySignatureBuilder.cs new file mode 100644 index 0000000..b1eb641 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/KeySignatureBuilder.cs @@ -0,0 +1,424 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using Sanford.Multimedia; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Builds key signature MetaMessages. + /// + public class KeySignatureBuilder : IMessageBuilder + { + private Key key = Key.CMajor; + + private MetaMessage result = null; + + /// + /// Initializes a new instance of the KeySignatureBuilder class. + /// + public KeySignatureBuilder() + { + } + + /// + /// Initializes a new instance of the KeySignatureBuilder class with + /// the specified key signature MetaMessage. + /// + /// + /// The key signature MetaMessage to use for initializing the + /// KeySignatureBuilder class. + /// + public KeySignatureBuilder(MetaMessage message) + { + Initialize(message); + } + + /// + /// Initializes the KeySignatureBuilder with the specified MetaMessage. + /// + /// + /// The key signature MetaMessage to use for initializing the + /// KeySignatureBuilder. + /// + public void Initialize(MetaMessage message) + { + #region Require + + if(message == null) + { + throw new ArgumentNullException("message"); + } + else if(message.MetaType != MetaType.KeySignature) + { + throw new ArgumentException("Wrong meta event type.", "messaege"); + } + + #endregion + + sbyte b = (sbyte)message[0]; + + // If the key is major. + if(message[1] == 0) + { + switch(b) + { + case -7: + key = Key.CFlatMajor; + break; + + case -6: + key = Key.GFlatMajor; + break; + + case -5: + key = Key.DFlatMajor; + break; + + case -4: + key = Key.AFlatMajor; + break; + + case -3: + key = Key.EFlatMajor; + break; + + case -2: + key = Key.BFlatMajor; + break; + + case -1: + key = Key.FMajor; + break; + + case 0: + key = Key.CMajor; + break; + + case 1: + key = Key.GMajor; + break; + + case 2: + key = Key.DMajor; + break; + + case 3: + key = Key.AMajor; + break; + + case 4: + key = Key.EMajor; + break; + + case 5: + key = Key.BMajor; + break; + + case 6: + key = Key.FSharpMajor; + break; + + case 7: + key = Key.CSharpMajor; + break; + } + + } + // Else the key is minor. + else + { + switch(b) + { + case -7: + key = Key.AFlatMinor; + break; + + case -6: + key = Key.EFlatMinor; + break; + + case -5: + key = Key.BFlatMinor; + break; + + case -4: + key = Key.FMinor; + break; + + case -3: + key = Key.CMinor; + break; + + case -2: + key = Key.GMinor; + break; + + case -1: + key = Key.DMinor; + break; + + case 0: + key = Key.AMinor; + break; + + case 1: + key = Key.EMinor; + break; + + case 2: + key = Key.BMinor; + break; + + case 3: + key = Key.FSharpMinor; + break; + + case 4: + key = Key.CSharpMinor; + break; + + case 5: + key = Key.GSharpMinor; + break; + + case 6: + key = Key.DSharpMinor; + break; + + case 7: + key = Key.ASharpMinor; + break; + } + } + } + + /// + /// Gets or sets the key. + /// + public Key Key + { + get + { + return key; + } + set + { + key = value; + } + } + + /// + /// The build key signature MetaMessage. + /// + public MetaMessage Result + { + get + { + return result; + } + } + + #region IMessageBuilder Members + + /// + /// Builds the key signature MetaMessage. + /// + public void Build() + { + byte[] data = new byte[MetaMessage.KeySigLength]; + + unchecked + { + switch(Key) + { + case Key.CFlatMajor: + data[0] = (byte)-7; + data[1] = 0; + break; + + case Key.GFlatMajor: + data[0] = (byte)-6; + data[1] = 0; + break; + + case Key.DFlatMajor: + data[0] = (byte)-5; + data[1] = 0; + break; + + case Key.AFlatMajor: + data[0] = (byte)-4; + data[1] = 0; + break; + + case Key.EFlatMajor: + data[0] = (byte)-3; + data[1] = 0; + break; + + case Key.BFlatMajor: + data[0] = (byte)-2; + data[1] = 0; + break; + + case Key.FMajor: + data[0] = (byte)-1; + data[1] = 0; + break; + + case Key.CMajor: + data[0] = 0; + data[1] = 0; + break; + + case Key.GMajor: + data[0] = 1; + data[1] = 0; + break; + + case Key.DMajor: + data[0] = 2; + data[1] = 0; + break; + + case Key.AMajor: + data[0] = 3; + data[1] = 0; + break; + + case Key.EMajor: + data[0] = 4; + data[1] = 0; + break; + + case Key.BMajor: + data[0] = 5; + data[1] = 0; + break; + + case Key.FSharpMajor: + data[0] = 6; + data[1] = 0; + break; + + case Key.CSharpMajor: + data[0] = 7; + data[1] = 0; + break; + + case Key.AFlatMinor: + data[0] = (byte)-7; + data[1] = 1; + break; + + case Key.EFlatMinor: + data[0] = (byte)-6; + data[1] = 1; + break; + + case Key.BFlatMinor: + data[0] = (byte)-5; + data[1] = 1; + break; + + case Key.FMinor: + data[0] = (byte)-4; + data[1] = 1; + break; + + case Key.CMinor: + data[0] = (byte)-3; + data[1] = 1; + break; + + case Key.GMinor: + data[0] = (byte)-2; + data[1] = 1; + break; + + case Key.DMinor: + data[0] = (byte)-1; + data[1] = 1; + break; + + case Key.AMinor: + data[0] = 1; + data[1] = 0; + break; + + case Key.EMinor: + data[0] = 1; + data[1] = 1; + break; + + case Key.BMinor: + data[0] = 2; + data[1] = 1; + break; + + case Key.FSharpMinor: + data[0] = 3; + data[1] = 1; + break; + + case Key.CSharpMinor: + data[0] = 4; + data[1] = 1; + break; + + case Key.GSharpMinor: + data[0] = 5; + data[1] = 1; + break; + + case Key.DSharpMinor: + data[0] = 6; + data[1] = 1; + break; + + case Key.ASharpMinor: + data[0] = 7; + data[1] = 1; + break; + } + } + + result = new MetaMessage(MetaType.KeySignature, data); + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/MetaTextBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/MetaTextBuilder.cs new file mode 100644 index 0000000..e980961 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/MetaTextBuilder.cs @@ -0,0 +1,416 @@ +#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.Text; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides functionality for building meta text messages. + /// + public class MetaTextBuilder : IMessageBuilder + { + #region MetaTextBuilder Members + + #region Fields + + // The text represented by the MetaMessage. + private string text; + + // The MetaMessage type - must be one of the text based types. + private MetaType type = MetaType.Text; + + // The built MetaMessage. + private MetaMessage result = null; + + // Indicates whether or not the text has changed since the message was + // last built. + private bool changed = true; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the MetaMessageTextBuilder class. + /// + public MetaTextBuilder() + { + text = string.Empty; + } + + /// + /// Initializes a new instance of the MetaMessageTextBuilder class with the + /// specified type. + /// + /// + /// The type of MetaMessage. + /// + /// + /// If the MetaMessage type is not a text based type. + /// + /// + /// The MetaMessage type must be one of the following text based + /// types: + /// + /// + /// Copyright + /// + /// + /// Cuepoint + /// + /// + /// DeviceName + /// + /// + /// InstrumentName + /// + /// + /// Lyric + /// + /// + /// Marker + /// + /// + /// ProgramName + /// + /// + /// Text + /// + /// + /// TrackName + /// + /// + /// If the MetaMessage is not a text based type, an exception + /// will be thrown. + /// + public MetaTextBuilder(MetaType type) + { + #region Require + + if(!IsTextType(type)) + { + throw new ArgumentException("Not text based meta message type.", + "message"); + } + + #endregion + + this.text = string.Empty; + } + + /// + /// Initializes a new instance of the MetaMessageTextBuilder class with the + /// specified type. + /// + /// + /// The type of MetaMessage. + /// + /// + /// If the MetaMessage type is not a text based type. + /// + /// + /// The MetaMessage type must be one of the following text based + /// types: + /// + /// + /// Copyright + /// + /// + /// Cuepoint + /// + /// + /// DeviceName + /// + /// + /// InstrumentName + /// + /// + /// Lyric + /// + /// + /// Marker + /// + /// + /// ProgramName + /// + /// + /// Text + /// + /// + /// TrackName + /// + /// + /// If the MetaMessage is not a text based type, an exception + /// will be thrown. + /// + public MetaTextBuilder(MetaType type, string text) + { + #region Require + + if(!IsTextType(type)) + { + throw new ArgumentException("Not text based meta message type.", + "message"); + } + + #endregion + + this.type = type; + + if(text != null) + { + this.text = text; + } + else + { + this.text = string.Empty; + } + } + + + /// + /// Initializes a new instance of the MetaMessageTextBuilder class with the + /// specified MetaMessage. + /// + /// + /// The MetaMessage to use for initializing the MetaMessageTextBuilder. + /// + /// + /// If the MetaMessage is not a text based type. + /// + /// + /// The MetaMessage must be one of the following text based types: + /// + /// + /// Copyright + /// + /// + /// Cuepoint + /// + /// + /// DeviceName + /// + /// + /// InstrumentName + /// + /// + /// Lyric + /// + /// + /// Marker + /// + /// + /// ProgramName + /// + /// + /// Text + /// + /// + /// TrackName + /// + /// + /// If the MetaMessage is not a text based type, an exception will be + /// thrown. + /// + public MetaTextBuilder(MetaMessage message) + { + Initialize(message); + } + + #endregion + + #region Methods + + /// + /// Initializes the MetaMessageTextBuilder with the specified MetaMessage. + /// + /// + /// The MetaMessage to use for initializing the MetaMessageTextBuilder. + /// + /// + /// If the MetaMessage is not a text based type. + /// + public void Initialize(MetaMessage message) + { + #region Require + + if(!IsTextType(message.MetaType)) + { + throw new ArgumentException("Not text based meta message.", + "message"); + } + + #endregion + + ASCIIEncoding encoding = new ASCIIEncoding(); + + text = encoding.GetString(message.GetBytes()); + this.type = message.MetaType; + } + + /// + /// Indicates whether or not the specified MetaType is a text based + /// type. + /// + /// + /// The MetaType to test. + /// + /// + /// true if the MetaType is a text based type; + /// otherwise, false. + /// + private bool IsTextType(MetaType type) + { + bool result; + + if(type == MetaType.Copyright || + type == MetaType.CuePoint || + type == MetaType.DeviceName || + type == MetaType.InstrumentName || + type == MetaType.Lyric || + type == MetaType.Marker || + type == MetaType.ProgramName || + type == MetaType.Text || + type == MetaType.TrackName) + { + result = true; + } + else + { + result = false; + } + + return result; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the text for the MetaMessage. + /// + public string Text + { + get + { + return text; + } + set + { + if(value != null) + { + text = value; + } + else + { + text = string.Empty; + } + + changed = true; + } + } + + /// + /// Gets or sets the MetaMessage type. + /// + /// + /// If the type is not a text based type. + /// + public MetaType Type + { + get + { + return type; + } + set + { + #region Require + + if(!IsTextType(value)) + { + throw new ArgumentException("Not text based meta message type.", + "message"); + } + + #endregion + + type = value; + + changed = true; + } + } + + /// + /// Gets the built MetaMessage. + /// + public MetaMessage Result + { + get + { + return result; + } + } + + #endregion + + #endregion + + #region IMessageBuilder Members + + /// + /// Builds the text MetaMessage. + /// + public void Build() + { + // If the text has changed since the last time this method was + // called. + if(changed) + { + // + // Build text MetaMessage. + // + + ASCIIEncoding encoding = new ASCIIEncoding(); + byte[] data = encoding.GetBytes(text); + result = new MetaMessage(Type, data); + changed = false; + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/SongPositionPointerBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/SongPositionPointerBuilder.cs new file mode 100644 index 0000000..4222db0 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/SongPositionPointerBuilder.cs @@ -0,0 +1,266 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides functionality for building song position pointer messages. + /// + public class SongPositionPointerBuilder : IMessageBuilder + { + #region SongPositionPointerBuilder Members + + #region Constants + + // The number of ticks per 16th note. + private const int TicksPer16thNote = 6; + + // Used for packing and unpacking the song position. + private const int Shift = 7; + + // Used for packing and unpacking the song position. + private const int Mask = 127; + + #endregion + + #region Fields + + // The scale used for converting from the song position to the position + // in ticks. + private int tickScale; + + // Pulses per quarter note resolution. + private int ppqn; + + // Used for building the SysCommonMessage to represent the song + // position pointer. + private SysCommonMessageBuilder builder; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the SongPositionPointerBuilder class. + /// + public SongPositionPointerBuilder() + { + builder = new SysCommonMessageBuilder(); + builder.Type = SysCommonType.SongPositionPointer; + + Ppqn = PpqnClock.PpqnMinValue; + } + + /// + /// Initializes a new instance of the SongPositionPointerBuilder class + /// with the specified song position pointer message. + /// + /// + /// The song position pointer message to use for initializing the + /// SongPositionPointerBuilder. + /// + /// + /// If message is not a song position pointer message. + /// + public SongPositionPointerBuilder(SysCommonMessage message) + { + builder = new SysCommonMessageBuilder(); + builder.Type = SysCommonType.SongPositionPointer; + + Initialize(message); + + Ppqn = PpqnClock.PpqnMinValue; + } + + #endregion + + #region Methods + + /// + /// Initializes the SongPositionPointerBuilder with the specified + /// SysCommonMessage. + /// + /// + /// The SysCommonMessage to use to initialize the + /// SongPositionPointerBuilder. + /// + /// + /// If the SysCommonMessage is not a song position pointer message. + /// + public void Initialize(SysCommonMessage message) + { + #region Require + + if(message == null) + { + throw new ArgumentNullException("message"); + } + else if(message.SysCommonType != SysCommonType.SongPositionPointer) + { + throw new ArgumentException( + "Message is not a song position pointer message."); + } + + #endregion + + builder.Initialize(message); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the sequence position in ticks. + /// + /// + /// Value is set to less than zero. + /// + /// + /// Note: the position in ticks value is converted to the song position + /// pointer value. Since the song position pointer has a lower + /// resolution than the position in ticks, there is a probable loss of + /// resolution when setting the position in ticks value. + /// + public int PositionInTicks + { + get + { + return SongPosition * tickScale * TicksPer16thNote; + } + set + { + #region Require + + if(value < 0) + { + throw new ArgumentOutOfRangeException("PositionInTicks", value, + "Position in ticks out of range."); + } + + #endregion + + SongPosition = value / (tickScale * TicksPer16thNote); + } + } + + /// + /// Gets or sets the PulsesPerQuarterNote object. + /// + /// + /// Value is not a multiple of 24. + /// + public int Ppqn + { + get + { + return ppqn; + } + set + { + #region Require + + if(value % PpqnClock.PpqnMinValue != 0) + { + throw new ArgumentException( + "Invalid pulses per quarter note value."); + } + + #endregion + + ppqn = value; + + tickScale = ppqn / PpqnClock.PpqnMinValue; + } + } + + /// + /// Gets or sets the song position. + /// + /// + /// Value is set to less than zero. + /// + public int SongPosition + { + get + { + return (builder.Data2 << Shift) | builder.Data1; + } + set + { + #region Require + + if(value < 0) + { + throw new ArgumentOutOfRangeException("SongPosition", value, + "Song position pointer out of range."); + } + + #endregion + + builder.Data1 = value & Mask; + builder.Data2 = value >> Shift; + } + } + + /// + /// Gets the built song position pointer message. + /// + public SysCommonMessage Result + { + get + { + return builder.Result; + } + } + + #endregion + + #endregion + + #region IMessageBuilder Members + + /// + /// Builds a song position pointer message. + /// + public void Build() + { + builder.Build(); + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/SysCommonMessageBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/SysCommonMessageBuilder.cs new file mode 100644 index 0000000..d79a1b9 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/SysCommonMessageBuilder.cs @@ -0,0 +1,233 @@ +#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.Collections; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides functionality for building SysCommonMessages. + /// + public class SysCommonMessageBuilder : IMessageBuilder + { + #region SysCommonMessageBuilder Members + + #region Class Fields + + // Stores the SystemCommonMessages. + private static Hashtable messageCache = Hashtable.Synchronized(new Hashtable()); + + #endregion + + #region Fields + + // The SystemCommonMessage as a packed integer. + private int message = 0; + + // The built SystemCommonMessage. + private SysCommonMessage result = null; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the SysCommonMessageBuilder class. + /// + public SysCommonMessageBuilder() + { + Type = SysCommonType.TuneRequest; + } + + /// + /// Initializes a new instance of the SysCommonMessageBuilder class + /// with the specified SystemCommonMessage. + /// + /// + /// The SysCommonMessage to use for initializing the + /// SysCommonMessageBuilder. + /// + /// + /// The SysCommonMessageBuilder uses the specified SysCommonMessage to + /// initialize its property values. + /// + public SysCommonMessageBuilder(SysCommonMessage message) + { + Initialize(message); + } + + #endregion + + #region Methods + + /// + /// Initializes the SysCommonMessageBuilder with the specified + /// SysCommonMessage. + /// + /// + /// The SysCommonMessage to use for initializing the + /// SysCommonMessageBuilder. + /// + public void Initialize(SysCommonMessage message) + { + this.message = message.Message; + } + + /// + /// Clears the SysCommonMessageBuilder cache. + /// + public static void Clear() + { + messageCache.Clear(); + } + + #endregion + + #region Properties + + /// + /// Gets the number of messages in the SysCommonMessageBuilder cache. + /// + public static int Count + { + get + { + return messageCache.Count; + } + } + + /// + /// Gets the built SysCommonMessage. + /// + public SysCommonMessage Result + { + get + { + return result; + } + } + + /// + /// Gets or sets the SysCommonMessage as a packed integer. + /// + internal int Message + { + get + { + return message; + } + set + { + message = value; + } + } + + /// + /// Gets or sets the type of SysCommonMessage. + /// + public SysCommonType Type + { + get + { + return (SysCommonType)ShortMessage.UnpackStatus(message); + } + set + { + message = ShortMessage.PackStatus(message, (int)value); + } + } + + /// + /// Gets or sets the first data value to use for building the + /// SysCommonMessage. + /// + /// + /// Data1 is set to a value less than zero or greater than 127. + /// + public int Data1 + { + get + { + return ShortMessage.UnpackData1(message); + } + set + { + message = ShortMessage.PackData1(message, value); + } + } + + /// + /// Gets or sets the second data value to use for building the + /// SysCommonMessage. + /// + /// + /// Data2 is set to a value less than zero or greater than 127. + /// + public int Data2 + { + get + { + return ShortMessage.UnpackData2(message); + } + set + { + message = ShortMessage.PackData2(message, value); + } + } + + #endregion + + #endregion + + #region IMessageBuilder Members + + /// + /// Builds a SysCommonMessage. + /// + public void Build() + { + result = (SysCommonMessage)messageCache[message]; + + if(result == null) + { + result = new SysCommonMessage(message); + + messageCache.Add(message, result); + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/TempoChangeBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/TempoChangeBuilder.cs new file mode 100644 index 0000000..0c0cbc6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/TempoChangeBuilder.cs @@ -0,0 +1,242 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides functionality for building tempo messages. + /// + public class TempoChangeBuilder : IMessageBuilder + { + #region TempoChangeBuilder Members + + #region Constants + + // Value used for shifting bits for packing and unpacking tempo values. + private const int Shift = 8; + + #endregion + + #region Fields + + // The mesage's tempo. + private int tempo = PpqnClock.DefaultTempo; + + // The built MetaMessage. + private MetaMessage result = null; + + // Indicates whether the tempo property has been changed since + // the last time the message was built. + private bool changed = true; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the TempoChangeBuilder class. + /// + public TempoChangeBuilder() + { + } + + /// + /// Initialize a new instance of the TempoChangeBuilder class with the + /// specified MetaMessage. + /// + /// + /// The MetaMessage to use for initializing the TempoChangeBuilder class. + /// + /// + /// If the specified MetaMessage is not a tempo type. + /// + /// + /// The TempoChangeBuilder uses the specified MetaMessage to initialize + /// its property values. + /// + public TempoChangeBuilder(MetaMessage e) + { + Initialize(e); + } + + #endregion + + #region Methods + + /// + /// Initializes the TempoChangeBuilder with the specified MetaMessage. + /// + /// + /// The MetaMessage to use for initializing the TempoChangeBuilder. + /// + /// + /// If the specified MetaMessage is not a tempo type. + /// + public void Initialize(MetaMessage e) + { + #region Require + + if(e == null) + { + throw new ArgumentNullException("e"); + } + else if(e.MetaType != MetaType.Tempo) + { + throw new ArgumentException("Wrong meta message type.", "e"); + } + + #endregion + + int t = 0; + + // If this platform uses little endian byte order. + if(BitConverter.IsLittleEndian) + { + int d = e.Length - 1; + + // Pack tempo. + for(int i = 0; i < e.Length; i++) + { + t |= e[d] << (Shift * i); + d--; + } + } + // Else this platform uses big endian byte order. + else + { + // Pack tempo. + for(int i = 0; i < e.Length; i++) + { + t |= e[i] << (Shift * i); + } + } + + tempo = t; + } + + #endregion + + #region Properties + + /// + /// Gets or sets the tempo. + /// + /// + /// Value is set to less than zero. + /// + public int Tempo + { + get + { + return tempo; + } + set + { + #region Require + + if(value < 0) + { + throw new ArgumentOutOfRangeException("Tempo", value, + "Tempo is out of range."); + } + + #endregion + + tempo = value; + + changed = true; + } + } + + /// + /// Gets the built message. + /// + public MetaMessage Result + { + get + { + return result; + } + } + + #endregion + + #endregion + + #region IMessageBuilder Members + + /// + /// Builds the tempo change MetaMessage. + /// + public void Build() + { + // If the tempo has been changed since the last time the message + // was built. + if(changed) + { + byte[] data = new byte[MetaMessage.TempoLength]; + + // If this platform uses little endian byte order. + if(BitConverter.IsLittleEndian) + { + int d = data.Length - 1; + + // Unpack tempo. + for(int i = 0; i < data.Length; i++) + { + data[d] = (byte)(tempo >> (Shift * i)); + d--; + } + } + // Else this platform uses big endian byte order. + else + { + // Unpack tempo. + for(int i = 0; i < data.Length; i++) + { + data[i] = (byte)(tempo >> (Shift * i)); + } + } + + changed = false; + + result = new MetaMessage(MetaType.Tempo, data); + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/TimeSignatureBuilder.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/TimeSignatureBuilder.cs new file mode 100644 index 0000000..90b17ec --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/Message Builders/TimeSignatureBuilder.cs @@ -0,0 +1,277 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Provides easy to use functionality for time signature MetaMessages. + /// + public class TimeSignatureBuilder : IMessageBuilder + { + #region TimeSignature Members + + #region Constants + + // Default numerator value. + private const byte DefaultNumerator = 4; + + // Default denominator value. + private const byte DefaultDenominator = 2; + + // Default clocks per metronome click value. + private const byte DefaultClocksPerMetronomeClick = 24; + + // Default thirty second notes per quarter note value. + private const byte DefaultThirtySecondNotesPerQuarterNote = 8; + + #endregion + + #region Fields + + // The raw data making up the time signature meta message. + private byte[] data = new byte[MetaMessage.TimeSigLength]; + + // The built time signature meta message. + private MetaMessage result = null; + + // Indicates whether any of the properties have changed since the + // last time the message was built. + private bool changed = true; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the TimeSignatureBuilder class. + /// + public TimeSignatureBuilder() + { + Numerator = DefaultNumerator; + Denominator = DefaultDenominator; + ClocksPerMetronomeClick = DefaultClocksPerMetronomeClick; + ThirtySecondNotesPerQuarterNote = DefaultThirtySecondNotesPerQuarterNote; + } + + /// + /// Initializes a new instance of the TimeSignatureBuilder class with the + /// specified MetaMessage. + /// + /// + /// The MetaMessage to use for initializing the TimeSignatureBuilder class. + /// + /// + /// If the specified MetaMessage is not a time signature type. + /// + /// + /// The TimeSignatureBuilder uses the specified MetaMessage to + /// initialize its property values. + /// + public TimeSignatureBuilder(MetaMessage message) + { + Initialize(message); + } + + #endregion + + #region Methods + + /// + /// Initializes the TimeSignatureBuilder with the specified MetaMessage. + /// + /// + /// The MetaMessage to use for initializing the TimeSignatureBuilder. + /// + /// + /// If the specified MetaMessage is not a time signature type. + /// + public void Initialize(MetaMessage message) + { + #region Require + + if(message.MetaType != MetaType.TimeSignature) + { + throw new ArgumentException("Wrong meta event type.", "message"); + } + + #endregion + + data = message.GetBytes(); + } + + #endregion + + #region Properties + + /// + /// Gets or sets the numerator. + /// + /// + /// Numerator is set to a value less than one. + /// + public byte Numerator + { + get + { + return data[0]; + } + set + { + #region Require + + if(value < 1) + { + throw new ArgumentOutOfRangeException("Numerator", value, + "Numerator out of range."); + } + + #endregion + + data[0] = value; + + changed = true; + } + } + + /// + /// Gets or sets the denominator. + /// + /// + /// Denominator is set to a value less than 2. + /// + /// + /// Denominator is set to a value that is not a power of 2. + /// + public byte Denominator + { + get + { + return Convert.ToByte(Math.Pow(2, data[1])); + } + set + { + #region Require + + if(value < 2 || value > 32) + { + throw new ArgumentOutOfRangeException("Denominator must be between 2 and 32."); + } + else if((value & (value - 1)) != 0) + { + throw new ArgumentException("Denominator must be a power of 2."); + } + + #endregion + + data[1] = Convert.ToByte(Math.Log(value, 2)); + + changed = true; + } + } + + /// + /// Gets or sets the clocks per metronome click. + /// + /// + /// Clocks per metronome click determines how many MIDI clocks occur + /// for each metronome click. + /// + public byte ClocksPerMetronomeClick + { + get + { + return data[2]; + } + set + { + data[2] = value; + + changed = true; + } + } + + /// + /// Gets or sets how many thirty second notes there are for each + /// quarter note. + /// + public byte ThirtySecondNotesPerQuarterNote + { + get + { + return data[3]; + } + set + { + data[3] = value; + + changed = true; + } + } + + /// + /// Gets the built message. + /// + public MetaMessage Result + { + get + { + return result; + } + } + + #endregion + + #endregion + + #region IMessageBuilder Members + + /// + /// Builds the time signature MetaMessage. + /// + public void Build() + { + // If any of the properties have changed since the last time the + // message was built. + if(changed) + { + result = new MetaMessage(MetaType.TimeSignature, data); + changed = false; + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/MessageDispatcher.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/MessageDispatcher.cs new file mode 100644 index 0000000..ef5d800 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/MessageDispatcher.cs @@ -0,0 +1,184 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Dispatches IMidiMessages to their corresponding sink. + /// + public class MessageDispatcher + { + #region MessageDispatcher Members + + #region Events + + public event EventHandler ChannelMessageDispatched; + + public event EventHandler SysExMessageDispatched; + + public event EventHandler SysCommonMessageDispatched; + + public event EventHandler SysRealtimeMessageDispatched; + + public event EventHandler MetaMessageDispatched; + + #endregion + + /// + /// Dispatches IMidiMessages to their corresponding sink. + /// + /// + /// The IMidiMessage to dispatch. + /// + public void Dispatch(IMidiMessage message) + { + #region Require + + if(message == null) + { + throw new ArgumentNullException("message"); + } + + #endregion + + switch(message.MessageType) + { + case MessageType.Channel: + OnChannelMessageDispatched(new ChannelMessageEventArgs((ChannelMessage)message)); + break; + + case MessageType.SystemExclusive: + OnSysExMessageDispatched(new SysExMessageEventArgs((SysExMessage)message)); + break; + + case MessageType.Meta: + OnMetaMessageDispatched(new MetaMessageEventArgs((MetaMessage)message)); + break; + + case MessageType.SystemCommon: + OnSysCommonMessageDispatched(new SysCommonMessageEventArgs((SysCommonMessage)message)); + break; + + case MessageType.SystemRealtime: + switch(((SysRealtimeMessage)message).SysRealtimeType) + { + case SysRealtimeType.ActiveSense: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.ActiveSense); + break; + + case SysRealtimeType.Clock: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.Clock); + break; + + case SysRealtimeType.Continue: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.Continue); + break; + + case SysRealtimeType.Reset: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.Reset); + break; + + case SysRealtimeType.Start: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.Start); + break; + + case SysRealtimeType.Stop: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.Stop); + break; + + case SysRealtimeType.Tick: + OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs.Tick); + break; + } + + break; + } + } + + protected virtual void OnChannelMessageDispatched(ChannelMessageEventArgs e) + { + EventHandler handler = ChannelMessageDispatched; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnSysExMessageDispatched(SysExMessageEventArgs e) + { + EventHandler handler = SysExMessageDispatched; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnSysCommonMessageDispatched(SysCommonMessageEventArgs e) + { + EventHandler handler = SysCommonMessageDispatched; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnSysRealtimeMessageDispatched(SysRealtimeMessageEventArgs e) + { + EventHandler handler = SysRealtimeMessageDispatched; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnMetaMessageDispatched(MetaMessageEventArgs e) + { + EventHandler handler = MetaMessageDispatched; + + if(handler != null) + { + handler(this, e); + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/MetaMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/MetaMessage.cs new file mode 100644 index 0000000..3b4478f --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/MetaMessage.cs @@ -0,0 +1,513 @@ +#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 + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/ShortMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/ShortMessage.cs new file mode 100644 index 0000000..f7d58af --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/ShortMessage.cs @@ -0,0 +1,179 @@ +#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; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents the basic class for all MIDI short messages. + /// + /// + /// MIDI short messages represent all MIDI messages except meta messages + /// and system exclusive messages. This includes channel messages, system + /// realtime messages, and system common messages. + /// + public abstract class ShortMessage : IMidiMessage + { + #region ShortMessage Members + + #region Constants + + public const int DataMaxValue= 127; + + public const int StatusMaxValue = 255; + + // + // Bit manipulation constants. + // + + private const int StatusMask = ~255; + protected const int DataMask = ~StatusMask; + private const int Data1Mask = ~65280; + private const int Data2Mask = ~Data1Mask + DataMask; + private const int Shift = 8; + + #endregion + + protected int msg = 0; + + #region Methods + + public byte[] GetBytes() + { + return BitConverter.GetBytes(msg); + } + + internal static int PackStatus(int message, int status) + { + #region Require + + if(status < 0 || status > StatusMaxValue) + { + throw new ArgumentOutOfRangeException("status", status, + "Status value out of range."); + } + + #endregion + + return (message & StatusMask) | status; + } + + internal static int PackData1(int message, int data1) + { + #region Require + + if(data1 < 0 || data1 > DataMaxValue) + { + throw new ArgumentOutOfRangeException("data1", data1, + "Data 1 value out of range."); + } + + #endregion + + return (message & Data1Mask) | (data1 << Shift); + } + + internal static int PackData2(int message, int data2) + { + #region Require + + if(data2 < 0 || data2 > DataMaxValue) + { + throw new ArgumentOutOfRangeException("data2", data2, + "Data 2 value out of range."); + } + + #endregion + + return (message & Data2Mask) | (data2 << (Shift * 2)); + } + + internal static int UnpackStatus(int message) + { + return message & DataMask; + } + + internal static int UnpackData1(int message) + { + return (message & ~Data1Mask) >> Shift; + } + + internal static int UnpackData2(int message) + { + return (message & ~Data2Mask) >> (Shift * 2); + } + + #endregion + + #region Properties + + /// + /// Gets the short message as a packed integer. + /// + /// + /// The message is packed into an integer value with the low-order byte + /// of the low-word representing the status value. The high-order byte + /// of the low-word represents the first data value, and the low-order + /// byte of the high-word represents the second data value. + /// + public int Message + { + get + { + return msg; + } + } + + /// + /// Gets the messages's status value. + /// + public int Status + { + get + { + return UnpackStatus(msg); + } + } + + public abstract MessageType MessageType + { + get; + } + + #endregion + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysCommonMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysCommonMessage.cs new file mode 100644 index 0000000..767391c --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysCommonMessage.cs @@ -0,0 +1,257 @@ +#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 System Common Message Types + + /// + /// Defines constants representing the various system common message types. + /// + public enum SysCommonType + { + /// + /// Represents the MTC system common message type. + /// + MidiTimeCode = 0xF1, + + /// + /// Represents the song position pointer type. + /// + SongPositionPointer, + + /// + /// Represents the song select type. + /// + SongSelect, + + /// + /// Represents the tune request type. + /// + TuneRequest = 0xF6 + } + + #endregion + + /// + /// Represents MIDI system common messages. + /// + [ImmutableObject(true)] + public sealed class SysCommonMessage : ShortMessage + { + #region SysCommonMessage Members + + #region Construction + + /// + /// Initializes a new instance of the SysCommonMessage class with the + /// specified type. + /// + /// + /// The type of SysCommonMessage. + /// + public SysCommonMessage(SysCommonType type) + { + msg = (int)type; + + #region Ensure + + Debug.Assert(SysCommonType == type); + + #endregion + } + + /// + /// Initializes a new instance of the SysCommonMessage class with the + /// specified type and the first data value. + /// + /// + /// The type of SysCommonMessage. + /// + /// + /// The first data value. + /// + /// + /// If data1 is less than zero or greater than 127. + /// + public SysCommonMessage(SysCommonType type, int data1) + { + msg = (int)type; + msg = PackData1(msg, data1); + + #region Ensure + + Debug.Assert(SysCommonType == type); + Debug.Assert(Data1 == data1); + + #endregion + } + + /// + /// Initializes a new instance of the SysCommonMessage class with the + /// specified type, first data value, and second data value. + /// + /// + /// The type of SysCommonMessage. + /// + /// + /// The first data value. + /// + /// + /// The second data value. + /// + /// + /// If data1 or data2 is less than zero or greater than 127. + /// + public SysCommonMessage(SysCommonType type, int data1, int data2) + { + msg = (int)type; + msg = PackData1(msg, data1); + msg = PackData2(msg, data2); + + #region Ensure + + Debug.Assert(SysCommonType == type); + Debug.Assert(Data1 == data1); + Debug.Assert(Data2 == data2); + + #endregion + } + + internal SysCommonMessage(int message) + { + this.msg = message; + } + + #endregion + + #region Methods + + /// + /// Returns a value for the current SysCommonMessage suitable for use + /// in hashing algorithms. + /// + /// + /// A hash code for the current SysCommonMessage. + /// + public override int GetHashCode() + { + return msg; + } + + /// + /// Determines whether two SysCommonMessage instances are equal. + /// + /// + /// The SysCommonMessage to compare with the current SysCommonMessage. + /// + /// + /// true if the specified SysCommonMessage is equal to the + /// current SysCommonMessage; otherwise, false. + /// + public override bool Equals(object obj) + { + #region Guard + + if(!(obj is SysCommonMessage)) + { + return false; + } + + #endregion + + SysCommonMessage message = (SysCommonMessage)obj; + + return (this.SysCommonType == message.SysCommonType && + this.Data1 == message.Data1 && + this.Data2 == message.Data2); + } + + #endregion + + #region Properties + + /// + /// Gets the SysCommonType. + /// + public SysCommonType SysCommonType + { + get + { + return (SysCommonType)UnpackStatus(msg); + } + } + + /// + /// Gets the first data value. + /// + public int Data1 + { + get + { + return UnpackData1(msg); + } + } + + /// + /// Gets the second data value. + /// + public int Data2 + { + get + { + return UnpackData2(msg); + } + } + + /// + /// Gets the MessageType. + /// + public override MessageType MessageType + { + get + { + return MessageType.SystemCommon; + } + } + + #endregion + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysExMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysExMessage.cs new file mode 100644 index 0000000..7ac8b16 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysExMessage.cs @@ -0,0 +1,254 @@ +#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.Collections; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Defines constants representing various system exclusive message types. + /// + public enum SysExType + { + /// + /// Represents the start of system exclusive message type. + /// + Start = 0xF0, + + /// + /// Represents the continuation of a system exclusive message. + /// + Continuation = 0xF7 + } + + /// + /// Represents MIDI system exclusive messages. + /// + public sealed class SysExMessage : IMidiMessage, IEnumerable + { + #region SysExEventMessage Members + + #region Constants + + /// + /// Maximum value for system exclusive channels. + /// + public const int SysExChannelMaxValue = 127; + + #endregion + + #region Fields + + // The system exclusive data. + private byte[] data; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the SysExMessageEventArgs class with the + /// specified system exclusive data. + /// + /// + /// The system exclusive data. + /// + /// + /// The system exclusive data's status byte, the first byte in the + /// data, must have a value of 0xF0 or 0xF7. + /// + public SysExMessage(byte[] data) + { + #region Require + + if(data.Length < 1) + { + throw new ArgumentException( + "System exclusive data is too short.", "data"); + } + else if(data[0] != (byte)SysExType.Start && + data[0] != (byte)SysExType.Continuation) + { + throw new ArgumentException( + "Unknown status value.", "data"); + } + + #endregion + + this.data = new byte[data.Length]; + data.CopyTo(this.data, 0); + } + + #endregion + + #region Methods + + public byte[] GetBytes() + { + byte[] clone = new byte[data.Length]; + + data.CopyTo(clone, 0); + + return clone; + } + + public void CopyTo(byte[] buffer, int index) + { + data.CopyTo(buffer, index); + } + + public override bool Equals(object obj) + { + #region Guard + + if(!(obj is SysExMessage)) + { + return false; + } + + #endregion + + SysExMessage message = (SysExMessage)obj; + + bool equals = true; + + if(this.Length != message.Length) + { + equals = false; + } + + for(int i = 0; i < this.Length && equals; i++) + { + if(this[i] != message[i]) + { + equals = false; + } + } + + return equals; + } + + public override int GetHashCode() + { + return data.GetHashCode(); + } + + #endregion + + #region Properties + + /// + /// Gets the element at the specified index. + /// + /// + /// If index is less than zero or greater than or equal to the length + /// of the message. + /// + public byte this[int index] + { + get + { + #region Require + + if(index < 0 || index >= Length) + { + throw new ArgumentOutOfRangeException("index", index, + "Index into system exclusive message out of range."); + } + + #endregion + + return data[index]; + } + } + + /// + /// Gets the length of the system exclusive data. + /// + public int Length + { + get + { + return data.Length; + } + } + + /// + /// Gets the system exclusive type. + /// + public SysExType SysExType + { + get + { + return (SysExType)data[0]; + } + } + + #endregion + + #endregion + + /// + /// Gets the status value. + /// + public int Status + { + get + { + return (int)data[0]; + } + } + + /// + /// Gets the MessageType. + /// + public MessageType MessageType + { + get + { + return MessageType.SystemExclusive; + } + } + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + return data.GetEnumerator(); + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysRealtimeMessage.cs b/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysRealtimeMessage.cs new file mode 100644 index 0000000..9544af6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Messages/SysRealtimeMessage.cs @@ -0,0 +1,227 @@ +#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 System Realtime Message Types + + /// + /// Defines constants representing the various system realtime message types. + /// + public enum SysRealtimeType + { + /// + /// Represents the clock system realtime type. + /// + Clock = 0xF8, + + /// + /// Represents the tick system realtime type. + /// + Tick, + + /// + /// Represents the start system realtime type. + /// + Start, + + /// + /// Represents the continue system realtime type. + /// + Continue, + + /// + /// Represents the stop system realtime type. + /// + Stop, + + /// + /// Represents the active sense system realtime type. + /// + ActiveSense = 0xFE, + + /// + /// Represents the reset system realtime type. + /// + Reset + } + + #endregion + + /// + /// Represents MIDI system realtime messages. + /// + /// + /// System realtime messages are MIDI messages that are primarily concerned + /// with controlling and synchronizing MIDI devices. + /// + [ImmutableObject(true)] + public sealed class SysRealtimeMessage : ShortMessage + { + #region SysRealtimeMessage Members + + #region System Realtime Messages + + /// + /// The instance of the system realtime start message. + /// + public static readonly SysRealtimeMessage StartMessage = + new SysRealtimeMessage(SysRealtimeType.Start); + + /// + /// The instance of the system realtime continue message. + /// + public static readonly SysRealtimeMessage ContinueMessage = + new SysRealtimeMessage(SysRealtimeType.Continue); + + /// + /// The instance of the system realtime stop message. + /// + public static readonly SysRealtimeMessage StopMessage = + new SysRealtimeMessage(SysRealtimeType.Stop); + + /// + /// The instance of the system realtime clock message. + /// + public static readonly SysRealtimeMessage ClockMessage = + new SysRealtimeMessage(SysRealtimeType.Clock); + + /// + /// The instance of the system realtime tick message. + /// + public static readonly SysRealtimeMessage TickMessage = + new SysRealtimeMessage(SysRealtimeType.Tick); + + /// + /// The instance of the system realtime active sense message. + /// + public static readonly SysRealtimeMessage ActiveSenseMessage = + new SysRealtimeMessage(SysRealtimeType.ActiveSense); + + /// + /// The instance of the system realtime reset message. + /// + public static readonly SysRealtimeMessage ResetMessage = + new SysRealtimeMessage(SysRealtimeType.Reset); + + #endregion + + // Make construction private so that a system realtime message cannot + // be constructed directly. + private SysRealtimeMessage(SysRealtimeType type) + { + msg = (int)type; + + #region Ensure + + Debug.Assert(SysRealtimeType == type); + + #endregion + } + + #region Methods + + /// + /// Returns a value for the current SysRealtimeMessage suitable for use in + /// hashing algorithms. + /// + /// + /// A hash code for the current SysRealtimeMessage. + /// + public override int GetHashCode() + { + return msg; + } + + /// + /// Determines whether two SysRealtimeMessage instances are equal. + /// + /// + /// The SysRealtimeMessage to compare with the current SysRealtimeMessage. + /// + /// + /// true if the specified SysRealtimeMessage is equal to the current + /// SysRealtimeMessage; otherwise, false. + /// + public override bool Equals(object obj) + { + #region Guard + + if(!(obj is SysRealtimeMessage)) + { + return false; + } + + #endregion + + SysRealtimeMessage message = (SysRealtimeMessage)obj; + + return this.msg == message.msg; + } + + #endregion + + #region Properties + + /// + /// Gets the SysRealtimeType. + /// + public SysRealtimeType SysRealtimeType + { + get + { + return (SysRealtimeType)msg; + } + } + + /// + /// Gets the MessageType. + /// + public override MessageType MessageType + { + get + { + return MessageType.SystemRealtime; + } + } + + #endregion + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/MidiNoteConverter.cs b/DependenciesCode/Sanford.Multimedia.Midi/MidiNoteConverter.cs new file mode 100644 index 0000000..942b54e --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/MidiNoteConverter.cs @@ -0,0 +1,156 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Converts a MIDI note number to its corresponding frequency. + /// + public sealed class MidiNoteConverter + { + /// + /// The minimum value a note ID can have. + /// + public const int NoteIDMinValue = 0; + + /// + /// The maximum value a note ID can have. + /// + public const int NoteIDMaxValue = 127; + + // Table for holding frequency values. + private readonly static double[] NoteToFrequencyTable = new double[NoteIDMaxValue + 1]; + + static MidiNoteConverter() + { + // The number of notes per octave. + int notesPerOctave = 12; + + // Reference frequency used for calculations. + double referenceFrequency = 440; + + // The note ID of the reference frequency. + int referenceNoteID = 69; + + double exponent; + + // Fill table with the frequencies of all MIDI notes. + for(int i = 0; i < NoteToFrequencyTable.Length; i++) + { + exponent = (double)(i - referenceNoteID) / notesPerOctave; + + NoteToFrequencyTable[i] = referenceFrequency * Math.Pow(2.0, exponent); + } + } + + // Prevents instances of this class from being created - no need for + // an instance to be created since this class only has static methods. + private MidiNoteConverter() + { + } + + /// + /// Converts the specified note to a frequency. + /// + /// + /// The ID of the note to convert. + /// + /// + /// The frequency of the specified note. + /// + public static double NoteToFrequency(int noteID) + { + #region Require + + if(noteID < NoteIDMinValue || noteID > NoteIDMaxValue) + { + throw new ArgumentOutOfRangeException("Note ID out of range."); + } + + #endregion + + return NoteToFrequencyTable[noteID]; + } + + /// + /// Converts the specified frequency to a note. + /// + /// + /// The frequency to convert. + /// + /// + /// The ID of the note closest to the specified frequency. + /// + public static int FrequencyToNote(double frequency) + { + int noteID = 0; + bool found = false; + + // Search for the note with a frequency near the specified frequency. + for(int i = 0; i < NoteIDMaxValue && !found; i++) + { + noteID = i; + + // If the specified frequency is less than the frequency of + // the next note. + if(frequency < NoteToFrequency(noteID + 1)) + { + // Indicate that the note ID for the specified frequency + // has been found. + found = true; + } + } + + // If the note is not the first or last note, narrow the results. + if(noteID > 0 && noteID < NoteIDMaxValue) + { + // Get the frequency of the previous note. + double previousFrequncy = NoteToFrequency(noteID - 1); + // Get the frequency of the next note. + double nextFrequency = NoteToFrequency(noteID + 1); + + // If the next note is closer in frequency than the previous note. + if(nextFrequency - frequency < frequency - previousFrequncy) + { + // Move to the next note. + noteID++; + } + } + + return noteID; + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChannelChaser.cs b/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChannelChaser.cs new file mode 100644 index 0000000..feca5a6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChannelChaser.cs @@ -0,0 +1,167 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections; + +namespace Sanford.Multimedia.Midi +{ + public class ChannelChaser + { + private ChannelMessage[,] controllerMessages; + + private ChannelMessage[] programChangeMessages; + + private ChannelMessage[] pitchBendMessages; + + private ChannelMessage[] channelPressureMessages; + + private ChannelMessage[] polyPressureMessages; + + public event EventHandler Chased; + + public ChannelChaser() + { + int c = ChannelMessage.MidiChannelMaxValue + 1; + int d = ShortMessage.DataMaxValue + 1; + + controllerMessages = new ChannelMessage[c, d]; + + programChangeMessages = new ChannelMessage[c]; + pitchBendMessages = new ChannelMessage[c]; + channelPressureMessages = new ChannelMessage[c]; + polyPressureMessages = new ChannelMessage[c]; + } + + public void Process(ChannelMessage message) + { + switch(message.Command) + { + case ChannelCommand.Controller: + controllerMessages[message.MidiChannel, message.Data1] = message; + break; + + case ChannelCommand.ChannelPressure: + channelPressureMessages[message.MidiChannel] = message; + break; + + case ChannelCommand.PitchWheel: + pitchBendMessages[message.MidiChannel] = message; + break; + + case ChannelCommand.PolyPressure: + polyPressureMessages[message.MidiChannel] = message; + break; + + case ChannelCommand.ProgramChange: + programChangeMessages[message.MidiChannel] = message; + break; + } + } + + public void Chase() + { + ArrayList chasedMessages = new ArrayList(); + + for(int c = 0; c <= ChannelMessage.MidiChannelMaxValue; c++) + { + for(int n = 0; n <= ShortMessage.DataMaxValue; n++) + { + if(controllerMessages[c, n] != null) + { + chasedMessages.Add(controllerMessages[c, n]); + + controllerMessages[c, n] = null; + } + } + + if(programChangeMessages[c] != null) + { + chasedMessages.Add(programChangeMessages[c]); + + programChangeMessages[c] = null; + } + + if(pitchBendMessages[c] != null) + { + chasedMessages.Add(pitchBendMessages[c]); + + pitchBendMessages[c] = null; + } + + if(channelPressureMessages[c] != null) + { + chasedMessages.Add(channelPressureMessages[c]); + + channelPressureMessages[c] = null; + } + + if(polyPressureMessages[c] != null) + { + chasedMessages.Add(polyPressureMessages[c]); + + polyPressureMessages[c] = null; + } + } + + OnChased(new ChasedEventArgs(chasedMessages)); + } + + public void Reset() + { + for(int c = 0; c <= ChannelMessage.MidiChannelMaxValue; c++) + { + for(int n = 0; n <= ShortMessage.DataMaxValue; n++) + { + controllerMessages[c, n] = null; + } + + programChangeMessages[c] = null; + pitchBendMessages[c] = null; + channelPressureMessages[c] = null; + polyPressureMessages[c] = null; + } + } + + protected virtual void OnChased(ChasedEventArgs e) + { + EventHandler handler = Chased; + + if(handler != null) + { + handler(this, e); + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChannelStopper.cs b/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChannelStopper.cs new file mode 100644 index 0000000..a663576 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChannelStopper.cs @@ -0,0 +1,211 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections; + +namespace Sanford.Multimedia.Midi +{ + public class ChannelStopper + { + private ChannelMessage[,] noteOnMessage; + + private bool[] holdPedal1Message; + + private bool[] holdPedal2Message; + + private bool[] sustenutoMessage; + + private ChannelMessageBuilder builder = new ChannelMessageBuilder(); + + public event EventHandler Stopped; + + public ChannelStopper() + { + int c = ChannelMessage.MidiChannelMaxValue + 1; + int d = ShortMessage.DataMaxValue + 1; + + noteOnMessage = new ChannelMessage[c, d]; + + holdPedal1Message = new bool[c]; + holdPedal2Message = new bool[c]; + sustenutoMessage = new bool[c]; + } + + public void Process(ChannelMessage message) + { + switch(message.Command) + { + case ChannelCommand.NoteOn: + if(message.Data2 > 0) + { + noteOnMessage[message.MidiChannel, message.Data1] = message; + } + else + { + noteOnMessage[message.MidiChannel, message.Data1] = null; + } + break; + + case ChannelCommand.NoteOff: + noteOnMessage[message.MidiChannel, message.Data1] = null; + break; + + case ChannelCommand.Controller: + switch(message.Data1) + { + case (int)ControllerType.HoldPedal1: + if(message.Data2 > 63) + { + holdPedal1Message[message.MidiChannel] = true; + } + else + { + holdPedal1Message[message.MidiChannel] = false; + } + break; + + case (int)ControllerType.HoldPedal2: + if(message.Data2 > 63) + { + holdPedal2Message[message.MidiChannel] = true; + } + else + { + holdPedal2Message[message.MidiChannel] = false; + } + break; + + case (int)ControllerType.SustenutoPedal: + if(message.Data2 > 63) + { + sustenutoMessage[message.MidiChannel] = true; + } + else + { + sustenutoMessage[message.MidiChannel] = false; + } + break; + } + break; + } + } + + public void AllSoundOff() + { + ArrayList stoppedMessages = new ArrayList(); + + for(int c = 0; c <= ChannelMessage.MidiChannelMaxValue; c++) + { + for(int n = 0; n <= ShortMessage.DataMaxValue; n++) + { + if(noteOnMessage[c, n] != null) + { + builder.MidiChannel = c; + builder.Command = ChannelCommand.NoteOff; + builder.Data1 = noteOnMessage[c, n].Data1; + builder.Build(); + + stoppedMessages.Add(builder.Result); + + noteOnMessage[c, n] = null; + } + } + + if(holdPedal1Message[c]) + { + builder.MidiChannel = c; + builder.Command = ChannelCommand.Controller; + builder.Data1 = (int)ControllerType.HoldPedal1; + builder.Build(); + + stoppedMessages.Add(builder.Result); + + holdPedal1Message[c] = false; + } + + if(holdPedal2Message[c]) + { + builder.MidiChannel = c; + builder.Command = ChannelCommand.Controller; + builder.Data1 = (int)ControllerType.HoldPedal2; + builder.Build(); + + stoppedMessages.Add(builder.Result); + + holdPedal2Message[c] = false; + } + + if(sustenutoMessage[c]) + { + builder.MidiChannel = c; + builder.Command = ChannelCommand.Controller; + builder.Data1 = (int)ControllerType.SustenutoPedal; + builder.Build(); + + stoppedMessages.Add(builder.Result); + + sustenutoMessage[c] = false; + } + } + + OnStopped(new StoppedEventArgs(stoppedMessages)); + } + + public void Reset() + { + for(int c = 0; c <= ChannelMessage.MidiChannelMaxValue; c++) + { + for(int n = 0; n <= ShortMessage.DataMaxValue; n++) + { + noteOnMessage[c, n] = null; + } + + holdPedal1Message[c] = false; + holdPedal2Message[c] = false; + sustenutoMessage[c] = false; + } + } + + protected virtual void OnStopped(StoppedEventArgs e) + { + EventHandler handler = Stopped; + + if(handler != null) + { + handler(this, e); + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChasedEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChasedEventArgs.cs new file mode 100644 index 0000000..678d68b --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Processing/ChasedEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class ChasedEventArgs : EventArgs + { + private ICollection messages; + + public ChasedEventArgs(ICollection messages) + { + this.messages = messages; + } + + public ICollection Messages + { + get + { + return messages; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Processing/StoppedEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/Processing/StoppedEventArgs.cs new file mode 100644 index 0000000..07cbee7 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Processing/StoppedEventArgs.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class StoppedEventArgs : EventArgs + { + private ICollection messages; + + public StoppedEventArgs(ICollection messages) + { + this.messages = messages; + } + + public ICollection Messages + { + get + { + return messages; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sanford.Multimedia.Midi.csproj b/DependenciesCode/Sanford.Multimedia.Midi/Sanford.Multimedia.Midi.csproj new file mode 100644 index 0000000..a4a202a --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sanford.Multimedia.Midi.csproj @@ -0,0 +1,296 @@ + + + Local + 8.0.50727 + 2.0 + {4269C72A-8D3A-4737-8F89-72EAA33EA9E1} + Debug + AnyCPU + + + + + Sanford.Multimedia.Midi + + + JScript + Grid + IE50 + false + Library + Sanford.Multimedia.Midi + OnBuildSuccess + + + + + + + v2.0 + 2.0 + + + bin\Debug\ + false + 285212672 + false + + + DEBUG;TRACE + + + true + 4096 + false + + + false + false + false + false + 4 + full + prompt + + + bin\Release\ + false + 285212672 + false + + + DEBUG;TRACE + + + false + 4096 + false + + + true + false + false + false + 4 + none + prompt + + + + False + ..\Dependencies\Sanford.Collections.dll + + + False + ..\Dependencies\Sanford.Multimedia.dll + + + False + ..\Dependencies\Sanford.Multimedia.Timers.dll + + + False + ..\Dependencies\Sanford.Threading.dll + + + System + + + System.Data + + + + + System.XML + + + + + Code + + + Code + + + Code + + + + + Code + + + + + + + Code + + + Code + + + Code + + + Code + + + + Code + + + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + Code + + + + Code + + + + Code + + + Code + + + Code + + + Code + + + + + + Code + + + Code + + + + + + + + + + + + + + + + + + + + Code + + + + Form + + + DeviceDialog.cs + + + Form + + + InputDeviceDialog.cs + + + Form + + + OutputDeviceDialog.cs + + + Component + + + Component + + + Form + + + PianoControlDialog.cs + + + + + + Designer + DeviceDialog.cs + + + Designer + InputDeviceDialog.cs + + + Designer + OutputDeviceDialog.cs + + + Designer + PianoControlDialog.cs + + + + + + + + + + \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/MidiEvent.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/MidiEvent.cs new file mode 100644 index 0000000..cc3e84b --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/MidiEvent.cs @@ -0,0 +1,148 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; + +namespace Sanford.Multimedia.Midi +{ + public class MidiEvent + { + private object owner = null; + + private int absoluteTicks; + + private IMidiMessage message; + + private MidiEvent next = null; + + private MidiEvent previous = null; + + internal MidiEvent(object owner, int absoluteTicks, IMidiMessage message) + { + #region Require + + if(owner == null) + { + throw new ArgumentNullException("owner"); + } + else if(absoluteTicks < 0) + { + throw new ArgumentOutOfRangeException("absoluteTicks", absoluteTicks, + "Absolute ticks out of range."); + } + else if(message == null) + { + throw new ArgumentNullException("e"); + } + + #endregion + + this.owner = owner; + this.absoluteTicks = absoluteTicks; + this.message = message; + } + + internal void SetAbsoluteTicks(int absoluteTicks) + { + this.absoluteTicks = absoluteTicks; + } + + internal object Owner + { + get + { + return owner; + } + } + + public int AbsoluteTicks + { + get + { + return absoluteTicks; + } + } + + public int DeltaTicks + { + get + { + int deltaTicks; + + if(Previous != null) + { + deltaTicks = AbsoluteTicks - previous.AbsoluteTicks; + } + else + { + deltaTicks = AbsoluteTicks; + } + + return deltaTicks; + } + } + + public IMidiMessage MidiMessage + { + get + { + return message; + } + } + + internal MidiEvent Next + { + get + { + return next; + } + set + { + next = value; + } + } + + internal MidiEvent Previous + { + get + { + return previous; + } + set + { + previous = value; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/MidiFileProperties.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/MidiFileProperties.cs new file mode 100644 index 0000000..54cede3 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/MidiFileProperties.cs @@ -0,0 +1,384 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Diagnostics; +using System.IO; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Defintes constants representing SMPTE frame rates. + /// + public enum SmpteFrameRate + { + Smpte24 = 24, + Smpte25 = 25, + Smpte30Drop = 29, + Smpte30 = 30 + } + + /// + /// The different types of sequences. + /// + public enum SequenceType + { + Ppqn, + Smpte + } + + /// + /// Represents MIDI file properties. + /// + 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) + { + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/RecordingSession.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/RecordingSession.cs new file mode 100644 index 0000000..26c5f4f --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/RecordingSession.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Sanford.Multimedia.Midi +{ + public class RecordingSession + { + private IClock clock; + + private List buffer = new List(); + + private Track result = new Track(); + + public RecordingSession(IClock clock) + { + this.clock = clock; + } + + public void Build() + { + result = new Track(); + + buffer.Sort(new TimestampComparer()); + + foreach(TimestampedMessage tm in buffer) + { + result.Insert(tm.ticks, tm.message); + } + } + + public void Clear() + { + buffer.Clear(); + } + + public Track Result + { + get + { + return result; + } + } + + public void Record(ChannelMessage message) + { + if(clock.IsRunning) + { + buffer.Add(new TimestampedMessage(clock.Ticks, message)); + } + } + + public void Record(SysExMessage message) + { + if(clock.IsRunning) + { + buffer.Add(new TimestampedMessage(clock.Ticks, message)); + } + } + + private struct TimestampedMessage + { + public int ticks; + + public IMidiMessage message; + + public TimestampedMessage(int ticks, IMidiMessage message) + { + this.ticks = ticks; + this.message = message; + } + } + + private class TimestampComparer : IComparer + { + #region IComparer Members + + public int Compare(TimestampedMessage x, TimestampedMessage y) + { + if(x.ticks > y.ticks) + { + return 1; + } + else if(x.ticks < y.ticks) + { + return -1; + } + else + { + return 0; + } + } + + #endregion + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Sequence.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Sequence.cs new file mode 100644 index 0000000..7cd339c --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Sequence.cs @@ -0,0 +1,773 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections; +using System.Collections.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents a collection of Tracks. + /// + public sealed class Sequence : IComponent, ICollection + { + #region Sequence Members + + #region Fields + + // The collection of Tracks for the Sequence. + private List tracks = new List(); + + // The Sequence's MIDI file properties. + private MidiFileProperties properties = new MidiFileProperties(); + + private BackgroundWorker loadWorker = new BackgroundWorker(); + + private BackgroundWorker saveWorker = new BackgroundWorker(); + + private ISite site = null; + + private bool disposed = false; + + #endregion + + #region Events + + public event EventHandler LoadCompleted; + + public event ProgressChangedEventHandler LoadProgressChanged; + + public event EventHandler SaveCompleted; + + public event ProgressChangedEventHandler SaveProgressChanged; + + #endregion + + #region Construction + + /// + /// Initializes a new instance of the Sequence class. + /// + public Sequence() + { + InitializeBackgroundWorkers(); + } + + /// + /// Initializes a new instance of the Sequence class with the specified division. + /// + /// + /// The Sequence's division value. + /// + public Sequence(int division) + { + properties.Division = division; + properties.Format = 1; + + InitializeBackgroundWorkers(); + } + + /// + /// Initializes a new instance of the Sequence class with the specified + /// file name of the MIDI file to load. + /// + /// + /// The name of the MIDI file to load. + /// + public Sequence(string fileName) + { + InitializeBackgroundWorkers(); + + Load(fileName); + } + + private void InitializeBackgroundWorkers() + { + loadWorker.DoWork += new DoWorkEventHandler(LoadDoWork); + loadWorker.ProgressChanged += new ProgressChangedEventHandler(OnLoadProgressChanged); + loadWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnLoadCompleted); + loadWorker.WorkerReportsProgress = true; + + saveWorker.DoWork += new DoWorkEventHandler(SaveDoWork); + saveWorker.ProgressChanged += new ProgressChangedEventHandler(OnSaveProgressChanged); + saveWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(OnSaveCompleted); + saveWorker.WorkerReportsProgress = true; + } + + #endregion + + #region Methods + + /// + /// Loads a MIDI file into the Sequence. + /// + /// + /// The MIDI file's name. + /// + public void Load(string fileName) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(IsBusy) + { + throw new InvalidOperationException(); + } + else if(fileName == null) + { + throw new ArgumentNullException("fileName"); + } + + #endregion + + FileStream stream = new FileStream(fileName, FileMode.Open, + FileAccess.Read, FileShare.Read); + + using(stream) + { + MidiFileProperties newProperties = new MidiFileProperties(); + TrackReader reader = new TrackReader(); + List newTracks = new List(); + + newProperties.Read(stream); + + for(int i = 0; i < newProperties.TrackCount; i++) + { + reader.Read(stream); + newTracks.Add(reader.Track); + } + + properties = newProperties; + tracks = newTracks; + } + + #region Ensure + + Debug.Assert(Count == properties.TrackCount); + + #endregion + } + + public void LoadAsync(string fileName) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(IsBusy) + { + throw new InvalidOperationException(); + } + else if(fileName == null) + { + throw new ArgumentNullException("fileName"); + } + + #endregion + + loadWorker.RunWorkerAsync(fileName); + } + + public void LoadAsyncCancel() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + loadWorker.CancelAsync(); + } + + /// + /// Saves the Sequence as a MIDI file. + /// + /// + /// The name to use for saving the MIDI file. + /// + public void Save(string fileName) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(fileName == null) + { + throw new ArgumentNullException("fileName"); + } + + #endregion + + FileStream stream = new FileStream(fileName, FileMode.Create, + FileAccess.Write, FileShare.None); + + using(stream) + { + properties.Write(stream); + + TrackWriter writer = new TrackWriter(); + + foreach(Track trk in tracks) + { + writer.Track = trk; + writer.Write(stream); + } + } + } + + public void SaveAsync(string fileName) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(IsBusy) + { + throw new InvalidOperationException(); + } + else if(fileName == null) + { + throw new ArgumentNullException("fileName"); + } + + #endregion + + saveWorker.RunWorkerAsync(fileName); + } + + public void SaveAsyncCancel() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + saveWorker.CancelAsync(); + } + + /// + /// Gets the length in ticks of the Sequence. + /// + /// + /// The length in ticks of the Sequence. + /// + /// + /// The length in ticks of the Sequence is represented by the Track + /// with the longest length. + /// + public int GetLength() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + int length = 0; + + foreach(Track t in this) + { + if(t.Length > length) + { + length = t.Length; + } + } + + return length; + } + + private void OnLoadCompleted(object sender, RunWorkerCompletedEventArgs e) + { + EventHandler handler = LoadCompleted; + + if(handler != null) + { + handler(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null)); + } + } + + private void OnLoadProgressChanged(object sender, ProgressChangedEventArgs e) + { + ProgressChangedEventHandler handler = LoadProgressChanged; + + if(handler != null) + { + handler(this, e); + } + } + + private void LoadDoWork(object sender, DoWorkEventArgs e) + { + string fileName = (string)e.Argument; + + FileStream stream = new FileStream(fileName, FileMode.Open, + FileAccess.Read, FileShare.Read); + + using(stream) + { + MidiFileProperties newProperties = new MidiFileProperties(); + TrackReader reader = new TrackReader(); + List newTracks = new List(); + + newProperties.Read(stream); + + float percentage; + + for(int i = 0; i < newProperties.TrackCount && !loadWorker.CancellationPending; i++) + { + reader.Read(stream); + newTracks.Add(reader.Track); + + percentage = (i + 1f) / newProperties.TrackCount; + + loadWorker.ReportProgress((int)(100 * percentage)); + } + + if(loadWorker.CancellationPending) + { + e.Cancel = true; + } + else + { + properties = newProperties; + tracks = newTracks; + } + } + } + + private void OnSaveCompleted(object sender, RunWorkerCompletedEventArgs e) + { + EventHandler handler = SaveCompleted; + + if(handler != null) + { + handler(this, new AsyncCompletedEventArgs(e.Error, e.Cancelled, null)); + } + } + + private void OnSaveProgressChanged(object sender, ProgressChangedEventArgs e) + { + ProgressChangedEventHandler handler = SaveProgressChanged; + + if(handler != null) + { + handler(this, e); + } + } + + private void SaveDoWork(object sender, DoWorkEventArgs e) + { + string fileName = (string)e.Argument; + + FileStream stream = new FileStream(fileName, FileMode.Create, + FileAccess.Write, FileShare.None); + + using(stream) + { + properties.Write(stream); + + TrackWriter writer = new TrackWriter(); + + float percentage; + + for(int i = 0; i < tracks.Count && !saveWorker.CancellationPending; i++) + { + writer.Track = tracks[i]; + writer.Write(stream); + + percentage = (i + 1f) / properties.TrackCount; + + saveWorker.ReportProgress((int)(100 * percentage)); + } + + if(saveWorker.CancellationPending) + { + e.Cancel = true; + } + } + } + + #endregion + + #region Properties + + /// + /// Gets the Track at the specified index. + /// + /// + /// The index of the Track to get. + /// + /// + /// The Track at the specified index. + /// + public Track this[int index] + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(index < 0 || index >= Count) + { + throw new ArgumentOutOfRangeException("index", index, + "Sequence index out of range."); + } + + #endregion + + return tracks[index]; + } + } + + /// + /// Gets the Sequence's division value. + /// + public int Division + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return properties.Division; + } + } + + /// + /// Gets or sets the Sequence's format value. + /// + public int Format + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return properties.Format; + } + set + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(IsBusy) + { + throw new InvalidOperationException(); + } + + #endregion + + properties.Format = value; + } + } + + /// + /// Gets the Sequence's type. + /// + public SequenceType SequenceType + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return properties.SequenceType; + } + } + + public bool IsBusy + { + get + { + return loadWorker.IsBusy || saveWorker.IsBusy; + } + } + + #endregion + + #endregion + + #region ICollection Members + + public void Add(Track item) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + else if(item == null) + { + throw new ArgumentNullException("item"); + } + + #endregion + + tracks.Add(item); + + properties.TrackCount = tracks.Count; + } + + public void Clear() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + tracks.Clear(); + + properties.TrackCount = tracks.Count; + } + + public bool Contains(Track item) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return tracks.Contains(item); + } + + public void CopyTo(Track[] array, int arrayIndex) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + tracks.CopyTo(array, arrayIndex); + } + + public int Count + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return tracks.Count; + } + } + + public bool IsReadOnly + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return false; + } + } + + public bool Remove(Track item) + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + bool result = tracks.Remove(item); + + if(result) + { + properties.TrackCount = tracks.Count; + } + + return result; + } + + #endregion + + #region IEnumerable Members + + public IEnumerator GetEnumerator() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return tracks.GetEnumerator(); + } + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException("Sequence"); + } + + #endregion + + return tracks.GetEnumerator(); + } + + #endregion + + #region IComponent Members + + public event EventHandler Disposed; + + public ISite Site + { + get + { + return site; + } + set + { + site = value; + } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + #region Guard + + if(disposed) + { + return; + } + + #endregion + + loadWorker.Dispose(); + saveWorker.Dispose(); + + disposed = true; + + EventHandler handler = Disposed; + + if(handler != null) + { + handler(this, EventArgs.Empty); + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Sequencer.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Sequencer.cs new file mode 100644 index 0000000..d19f33c --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Sequencer.cs @@ -0,0 +1,386 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; + +namespace Sanford.Multimedia.Midi +{ + public class Sequencer : IComponent + { + private Sequence sequence = null; + + private List> enumerators = new List>(); + + private MessageDispatcher dispatcher = new MessageDispatcher(); + + private ChannelChaser chaser = new ChannelChaser(); + + private ChannelStopper stopper = new ChannelStopper(); + + private MidiInternalClock clock = new MidiInternalClock(); + + private int tracksPlayingCount; + + private readonly object lockObject = new object(); + + private bool playing = false; + + private bool disposed = false; + + private ISite site = null; + + #region Events + + public event EventHandler PlayingCompleted; + + public event EventHandler ChannelMessagePlayed + { + add + { + dispatcher.ChannelMessageDispatched += value; + } + remove + { + dispatcher.ChannelMessageDispatched -= value; + } + } + + public event EventHandler SysExMessagePlayed + { + add + { + dispatcher.SysExMessageDispatched += value; + } + remove + { + dispatcher.SysExMessageDispatched -= value; + } + } + + public event EventHandler MetaMessagePlayed + { + add + { + dispatcher.MetaMessageDispatched += value; + } + remove + { + dispatcher.MetaMessageDispatched -= value; + } + } + + public event EventHandler Chased + { + add + { + chaser.Chased += value; + } + remove + { + chaser.Chased -= value; + } + } + + public event EventHandler Stopped + { + add + { + stopper.Stopped += value; + } + remove + { + stopper.Stopped -= value; + } + } + + #endregion + + public Sequencer() + { + dispatcher.MetaMessageDispatched += delegate(object sender, MetaMessageEventArgs e) + { + if(e.Message.MetaType == MetaType.EndOfTrack) + { + tracksPlayingCount--; + + if(tracksPlayingCount == 0) + { + Stop(); + + OnPlayingCompleted(EventArgs.Empty); + } + } + else + { + clock.Process(e.Message); + } + }; + + dispatcher.ChannelMessageDispatched += delegate(object sender, ChannelMessageEventArgs e) + { + stopper.Process(e.Message); + }; + + clock.Tick += delegate(object sender, EventArgs e) + { + lock(lockObject) + { + if(!playing) + { + return; + } + + foreach(IEnumerator enumerator in enumerators) + { + enumerator.MoveNext(); + } + } + }; + } + + ~Sequencer() + { + Dispose(false); + } + + protected virtual void Dispose(bool disposing) + { + if(disposing) + { + lock(lockObject) + { + Stop(); + + clock.Dispose(); + + disposed = true; + + GC.SuppressFinalize(this); + } + } + } + + public void Start() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + lock(lockObject) + { + Stop(); + + Position = 0; + + Continue(); + } + } + + public void Continue() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + #region Guard + + if(Sequence == null) + { + return; + } + + #endregion + + lock(lockObject) + { + Stop(); + + enumerators.Clear(); + + foreach(Track t in Sequence) + { + enumerators.Add(t.TickIterator(Position, chaser, dispatcher).GetEnumerator()); + } + + tracksPlayingCount = Sequence.Count; + + playing = true; + clock.Ppqn = sequence.Division; + clock.Continue(); + } + } + + public void Stop() + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + lock(lockObject) + { + #region Guard + + if(!playing) + { + return; + } + + #endregion + + playing = false; + clock.Stop(); + stopper.AllSoundOff(); + } + } + + protected virtual void OnPlayingCompleted(EventArgs e) + { + EventHandler handler = PlayingCompleted; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnDisposed(EventArgs e) + { + EventHandler handler = Disposed; + + if(handler != null) + { + handler(this, e); + } + } + + public int Position + { + get + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + + #endregion + + return clock.Ticks; + } + set + { + #region Require + + if(disposed) + { + throw new ObjectDisposedException(this.GetType().Name); + } + else if(value < 0) + { + throw new ArgumentOutOfRangeException(); + } + + #endregion + + bool wasPlaying; + + lock(lockObject) + { + wasPlaying = playing; + + Stop(); + + clock.SetTicks(value); + } + + lock(lockObject) + { + if(wasPlaying) + { + Continue(); + } + } + } + } + + public Sequence Sequence + { + get + { + return sequence; + } + set + { + #region Require + + if(value == null) + { + throw new ArgumentNullException(); + } + else if(value.SequenceType == SequenceType.Smpte) + { + throw new NotSupportedException(); + } + + #endregion + + lock(lockObject) + { + Stop(); + sequence = value; + } + } + } + + #region IComponent Members + + public event EventHandler Disposed; + + public ISite Site + { + get + { + return site; + } + set + { + site = value; + } + } + + #endregion + + #region IDisposable Members + + public void Dispose() + { + #region Guard + + if(disposed) + { + return; + } + + #endregion + + Dispose(true); + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.Iterators.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.Iterators.cs new file mode 100644 index 0000000..272f109 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.Iterators.cs @@ -0,0 +1,135 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Threading; + +namespace Sanford.Multimedia.Midi +{ + public sealed partial class Track + { + #region Iterators + + public IEnumerable Iterator() + { + MidiEvent current = head; + + while(current != null) + { + yield return current; + + current = current.Next; + } + + current = endOfTrackMidiEvent; + + yield return current; + } + + public IEnumerable DispatcherIterator(MessageDispatcher dispatcher) + { + IEnumerator enumerator = Iterator().GetEnumerator(); + + while(enumerator.MoveNext()) + { + yield return enumerator.Current.AbsoluteTicks; + + dispatcher.Dispatch(enumerator.Current.MidiMessage); + } + } + + public IEnumerable TickIterator(int startPosition, + ChannelChaser chaser, MessageDispatcher dispatcher) + { + #region Require + + if(startPosition < 0) + { + throw new ArgumentOutOfRangeException("startPosition", startPosition, + "Start position out of range."); + } + + #endregion + + IEnumerator enumerator = Iterator().GetEnumerator(); + + bool notFinished = enumerator.MoveNext(); + IMidiMessage message; + + while(notFinished && enumerator.Current.AbsoluteTicks < startPosition) + { + message = enumerator.Current.MidiMessage; + + if(message.MessageType == MessageType.Channel) + { + chaser.Process((ChannelMessage)message); + } + else if(message.MessageType == MessageType.Meta) + { + dispatcher.Dispatch(message); + } + + notFinished = enumerator.MoveNext(); + } + + chaser.Chase(); + + int ticks = startPosition; + + while(notFinished) + { + while(ticks < enumerator.Current.AbsoluteTicks) + { + yield return ticks; + + ticks++; + } + + yield return ticks; + + while(notFinished && enumerator.Current.AbsoluteTicks == ticks) + { + dispatcher.Dispatch(enumerator.Current.MidiMessage); + + notFinished = enumerator.MoveNext(); + } + + ticks++; + } + } + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.Test.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.Test.cs new file mode 100644 index 0000000..57192ee --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.Test.cs @@ -0,0 +1,128 @@ +using System; +using System.Diagnostics; + +namespace Sanford.Multimedia.Midi +{ + public sealed partial class Track + { + [Conditional("DEBUG")] + public static void Test() + { + TestInsert(); + TestRemoveAt(); + TestMerge(); + } + + [Conditional("DEBUG")] + private static void TestInsert() + { + Track track = new Track(); + int midiEventCount = 2000; + int positionMax = 32000; + int endOfTrackOffset = 1000; + int length = 0; + int position = 0; + ChannelMessage message = new ChannelMessage(ChannelCommand.NoteOff, 0, 60, 0); + Random r = new Random(); + + for(int i = 0; i < midiEventCount; i++) + { + position = r.Next(positionMax); + + if(position > length) + { + length = position; + } + + track.Insert(position, message); + } + + track.EndOfTrackOffset = endOfTrackOffset; + + length += track.EndOfTrackOffset; + + Debug.Assert(track.Count == midiEventCount + 1); + Debug.Assert(track.Length == length); + } + + [Conditional("DEBUG")] + private static void TestRemoveAt() + { + Track a = new Track(); + ChannelMessage message = new ChannelMessage(ChannelCommand.NoteOff, 0, 60, 0); + + a.Insert(0, message); + a.Insert(10, message); + a.Insert(20, message); + a.Insert(30, message); + a.Insert(40, message); + + int count = a.Count; + + a.RemoveAt(0); + + Debug.Assert(a.Count == count - 1); + + a.RemoveAt(a.Count - 2); + + Debug.Assert(a.Count == count - 2); + Debug.Assert(a.GetMidiEvent(0).AbsoluteTicks == 10); + Debug.Assert(a.GetMidiEvent(a.Count - 2).AbsoluteTicks == 30); + + a.RemoveAt(0); + a.RemoveAt(0); + a.RemoveAt(0); + + Debug.Assert(a.Count == 1); + } + + [Conditional("DEBUG")] + private static void TestMerge() + { + Track a = new Track(); + Track b = new Track(); + + a.Merge(b); + + Debug.Assert(a.Count == 1); + + ChannelMessage message = new ChannelMessage(ChannelCommand.NoteOff, 0, 60, 0); + + b.Insert(0, message); + b.Insert(10, message); + b.Insert(20, message); + b.Insert(30, message); + b.Insert(40, message); + + a.Merge(b); + + Debug.Assert(a.Count == 1 + b.Count - 1); + + a.Clear(); + + Debug.Assert(a.Count == 1); + + a.Insert(0, message); + a.Insert(10, message); + a.Insert(20, message); + a.Insert(30, message); + a.Insert(40, message); + + int count = a.Count; + + a.Merge(b); + + Debug.Assert(a.Count == count + b.Count - 1); + Debug.Assert(a.GetMidiEvent(0).DeltaTicks == 0); + Debug.Assert(a.GetMidiEvent(1).DeltaTicks == 0); + Debug.Assert(a.GetMidiEvent(2).DeltaTicks == 10); + Debug.Assert(a.GetMidiEvent(3).DeltaTicks == 0); + Debug.Assert(a.GetMidiEvent(4).DeltaTicks == 10); + Debug.Assert(a.GetMidiEvent(5).DeltaTicks == 0); + Debug.Assert(a.GetMidiEvent(6).DeltaTicks == 10); + Debug.Assert(a.GetMidiEvent(7).DeltaTicks == 0); + Debug.Assert(a.GetMidiEvent(8).DeltaTicks == 10); + Debug.Assert(a.GetMidiEvent(9).DeltaTicks == 0); + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.cs new file mode 100644 index 0000000..c0661c8 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/Track.cs @@ -0,0 +1,612 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Diagnostics; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Represents a collection of MidiEvents and a MIDI track within a + /// Sequence. + /// + public sealed partial class Track + { + #region Track Members + + #region Fields + + // The number of MidiEvents in the Track. Will always be at least 1 + // because the Track will always have an end of track message. + private int count = 1; + + // The number of ticks to offset the end of track message. + private int endOfTrackOffset = 0; + + // The first MidiEvent in the Track. + private MidiEvent head = null; + + // The last MidiEvent in the Track, not including the end of track + // message. + private MidiEvent tail = null; + + // The end of track MIDI event. + private MidiEvent endOfTrackMidiEvent; + + #endregion + + #region Construction + + public Track() + { + endOfTrackMidiEvent = new MidiEvent(this, Length, MetaMessage.EndOfTrackMessage); + } + + #endregion + + #region Methods + + /// + /// Inserts an IMidiMessage at the specified position in absolute ticks. + /// + /// + /// The position in the Track in absolute ticks in which to insert the + /// IMidiMessage. + /// + /// + /// The IMidiMessage to insert. + /// + public void Insert(int position, IMidiMessage message) + { + #region Require + + if(position < 0) + { + throw new ArgumentOutOfRangeException("position", position, + "IMidiMessage position out of range."); + } + else if(message == null) + { + throw new ArgumentNullException("message"); + } + + #endregion + + MidiEvent newMidiEvent = new MidiEvent(this, position, message); + + if(head == null) + { + head = newMidiEvent; + tail = newMidiEvent; + } + else if(position >= tail.AbsoluteTicks) + { + newMidiEvent.Previous = tail; + tail.Next = newMidiEvent; + tail = newMidiEvent; + endOfTrackMidiEvent.SetAbsoluteTicks(Length); + endOfTrackMidiEvent.Previous = tail; + } + else + { + MidiEvent current = head; + + while(current.AbsoluteTicks < position) + { + current = current.Next; + } + + newMidiEvent.Next = current; + newMidiEvent.Previous = current.Previous; + + if(current.Previous != null) + { + current.Previous.Next = newMidiEvent; + } + else + { + head = newMidiEvent; + } + + current.Previous = newMidiEvent; + } + + count++; + + #region Invariant + + AssertValid(); + + #endregion + } + + /// + /// Clears all of the MidiEvents, with the exception of the end of track + /// message, from the Track. + /// + public void Clear() + { + head = tail = null; + + count = 1; + + #region Invariant + + AssertValid(); + + #endregion + } + + /// + /// Merges the specified Track with the current Track. + /// + /// + /// The Track to merge with. + /// + public void Merge(Track trk) + { + #region Require + + if(trk == null) + { + throw new ArgumentNullException("trk"); + } + + #endregion + + #region Guard + + if(trk == this) + { + return; + } + else if(trk.Count == 1) + { + return; + } + + #endregion + +#if(DEBUG) + int oldCount = Count; +#endif + + count += trk.Count - 1; + + MidiEvent a = head; + MidiEvent b = trk.head; + MidiEvent current = null; + + Debug.Assert(b != null); + + if(a != null && a.AbsoluteTicks <= b.AbsoluteTicks) + { + current = new MidiEvent(this, a.AbsoluteTicks, a.MidiMessage); + a = a.Next; + } + else + { + current = new MidiEvent(this, b.AbsoluteTicks, b.MidiMessage); + b = b.Next; + } + + head = current; + + while(a != null && b != null) + { + while(a != null && a.AbsoluteTicks <= b.AbsoluteTicks) + { + current.Next = new MidiEvent(this, a.AbsoluteTicks, a.MidiMessage); + current.Next.Previous = current; + current = current.Next; + a = a.Next; + } + + if(a != null) + { + while(b != null && b.AbsoluteTicks <= a.AbsoluteTicks) + { + current.Next = new MidiEvent(this, b.AbsoluteTicks, b.MidiMessage); + current.Next.Previous = current; + current = current.Next; + b = b.Next; + } + } + } + + while(a != null) + { + current.Next = new MidiEvent(this, a.AbsoluteTicks, a.MidiMessage); + current.Next.Previous = current; + current = current.Next; + a = a.Next; + } + + while(b != null) + { + current.Next = new MidiEvent(this, b.AbsoluteTicks, b.MidiMessage); + current.Next.Previous = current; + current = current.Next; + b = b.Next; + } + + tail = current; + + endOfTrackMidiEvent.SetAbsoluteTicks(Length); + endOfTrackMidiEvent.Previous = tail; + + #region Ensure + + Debug.Assert(count == oldCount + trk.Count - 1); + + #endregion + + #region Invariant + + AssertValid(); + + #endregion + } + + /// + /// Removes the MidiEvent at the specified index. + /// + /// + /// The index into the Track at which to remove the MidiEvent. + /// + public void RemoveAt(int index) + { + #region Require + + if(index < 0) + { + throw new ArgumentOutOfRangeException("index", index, "Track index out of range."); + } + else if(index == Count - 1) + { + throw new ArgumentException("Cannot remove the end of track event.", "index"); + } + + #endregion + + MidiEvent current = GetMidiEvent(index); + + if(current.Previous != null) + { + current.Previous.Next = current.Next; + } + else + { + Debug.Assert(current == head); + + head = head.Next; + } + + if(current.Next != null) + { + current.Next.Previous = current.Previous; + } + else + { + Debug.Assert(current == tail); + + tail = tail.Previous; + + endOfTrackMidiEvent.SetAbsoluteTicks(Length); + endOfTrackMidiEvent.Previous = tail; + } + + current.Next = current.Previous = null; + + count--; + + #region Invariant + + AssertValid(); + + #endregion + } + + /// + /// Gets the MidiEvent at the specified index. + /// + /// + /// The index of the MidiEvent to get. + /// + /// + /// The MidiEvent at the specified index. + /// + public MidiEvent GetMidiEvent(int index) + { + #region Require + + if(index < 0 || index >= Count) + { + throw new ArgumentOutOfRangeException("index", index, + "Track index out of range."); + } + + #endregion + + MidiEvent result; + + if(index == Count - 1) + { + result = endOfTrackMidiEvent; + } + else + { + if(index < Count / 2) + { + result = head; + + for(int i = 0; i < index; i++) + { + result = result.Next; + } + } + else + { + result = tail; + + for(int i = Count - 2; i > index; i--) + { + result = result.Previous; + } + } + } + + #region Ensure + +#if(DEBUG) + if(index == Count - 1) + { + Debug.Assert(result.AbsoluteTicks == Length); + Debug.Assert(result.MidiMessage == MetaMessage.EndOfTrackMessage); + } + else + { + MidiEvent t = head; + + for(int i = 0; i < index; i++) + { + t = t.Next; + } + + Debug.Assert(t == result); + } +#endif + + #endregion + + return result; + } + + public void Move(MidiEvent e, int newPosition) + { + #region Require + + if(e.Owner != this) + { + throw new ArgumentException("MidiEvent does not belong to this Track."); + } + else if(newPosition < 0) + { + throw new ArgumentOutOfRangeException("newPosition"); + } + else if(e == endOfTrackMidiEvent) + { + throw new InvalidOperationException( + "Cannot move end of track message. Use the EndOfTrackOffset property instead."); + } + + #endregion + + MidiEvent previous = e.Previous; + MidiEvent next = e.Next; + + if(e.Previous != null && e.Previous.AbsoluteTicks > newPosition) + { + e.Previous.Next = e.Next; + + if(e.Next != null) + { + e.Next.Previous = e.Previous; + } + + while(previous != null && previous.AbsoluteTicks > newPosition) + { + next = previous; + previous = previous.Previous; + } + } + else if(e.Next != null && e.Next.AbsoluteTicks < newPosition) + { + e.Next.Previous = e.Previous; + + if(e.Previous != null) + { + e.Previous.Next = e.Next; + } + + while(next != null && next.AbsoluteTicks < newPosition) + { + previous = next; + next = next.Next; + } + } + + if(previous != null) + { + previous.Next = e; + } + + if(next != null) + { + next.Previous = e; + } + + e.Previous = previous; + e.Next = next; + e.SetAbsoluteTicks(newPosition); + + if(newPosition < head.AbsoluteTicks) + { + head = e; + } + + if(newPosition > tail.AbsoluteTicks) + { + tail = e; + } + + endOfTrackMidiEvent.SetAbsoluteTicks(Length); + endOfTrackMidiEvent.Previous = tail; + + #region Invariant + + AssertValid(); + + #endregion + } + + [Conditional("DEBUG")] + private void AssertValid() + { + int c = 1; + MidiEvent current = head; + int ticks = 1; + + while(current != null) + { + ticks += current.DeltaTicks; + + if(current.Previous != null) + { + Debug.Assert(current.AbsoluteTicks >= current.Previous.AbsoluteTicks); + Debug.Assert(current.DeltaTicks == current.AbsoluteTicks - current.Previous.AbsoluteTicks); + } + + if(current.Next == null) + { + Debug.Assert(tail == current); + } + + current = current.Next; + + c++; + } + + ticks += EndOfTrackOffset; + + Debug.Assert(ticks == Length, "Length mismatch"); + Debug.Assert(c == Count, "Count mismatch"); + } + + #endregion + + #region Properties + + /// + /// Gets the number of MidiEvents in the Track. + /// + public int Count + { + get + { + return count; + } + } + + /// + /// Gets the length of the Track in ticks. + /// + public int Length + { + get + { + int length = EndOfTrackOffset; + + if(tail != null) + { + length += tail.AbsoluteTicks; + } + + return length + 1; + } + } + + /// + /// Gets or sets the end of track meta message position offset. + /// + public int EndOfTrackOffset + { + get + { + return endOfTrackOffset; + } + set + { + #region Require + + if(value < 0) + { + throw new ArgumentOutOfRangeException("EndOfTrackOffset", value, + "End of track offset out of range."); + } + + #endregion + + endOfTrackOffset = value; + + endOfTrackMidiEvent.SetAbsoluteTicks(Length); + } + } + + /// + /// Gets an object that can be used to synchronize access to the Track. + /// + public object SyncRoot + { + get + { + return this; + } + } + + #endregion + + #endregion + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/TrackReader.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/TrackReader.cs new file mode 100644 index 0000000..863b963 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/TrackReader.cs @@ -0,0 +1,448 @@ +#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.IO; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Reads a track from a stream. + /// + internal class TrackReader + { + private Track track = new Track(); + + private Track newTrack = new Track(); + + private ChannelMessageBuilder cmBuilder = new ChannelMessageBuilder(); + + private SysCommonMessageBuilder scBuilder = new SysCommonMessageBuilder(); + + private Stream stream; + + private byte[] trackData; + + private int trackIndex; + + private int previousTicks; + + private int ticks; + + private int status; + + private int runningStatus; + + public TrackReader() + { + } + + public void Read(Stream strm) + { + stream = strm; + FindTrack(); + + int trackLength = GetTrackLength(); + trackData = new byte[trackLength]; + + int result = strm.Read(trackData, 0, trackLength); + + if(result < 0) + { + throw new MidiFileException("End of MIDI file unexpectedly reached."); + } + + newTrack = new Track(); + + ParseTrackData(); + + track = newTrack; + } + + private void FindTrack() + { + bool found = false; + int result; + + while(!found) + { + result = stream.ReadByte(); + + if(result == 'M') + { + result = stream.ReadByte(); + + if(result == 'T') + { + result = stream.ReadByte(); + + if(result == 'r') + { + result = stream.ReadByte(); + + if(result == 'k') + { + found = true; + } + } + } + } + + if(result < 0) + { + throw new MidiFileException("Unable to find track in MIDI file."); + } + } + } + + private int GetTrackLength() + { + byte[] trackLength = new byte[4]; + + int result = stream.Read(trackLength, 0, trackLength.Length); + + if(result < trackLength.Length) + { + throw new MidiFileException("End of MIDI file unexpectedly reached."); + } + + if(BitConverter.IsLittleEndian) + { + Array.Reverse(trackLength); + } + + return BitConverter.ToInt32(trackLength, 0); + } + + private void ParseTrackData() + { + trackIndex = ticks = runningStatus = 0; + + while(trackIndex < trackData.Length) + { + previousTicks = ticks; + + ticks += ReadVariableLengthValue(); + + if((trackData[trackIndex] & 0x80) == 0x80) + { + status = trackData[trackIndex]; + trackIndex++; + } + else + { + status = runningStatus; + } + + ParseMessage(); + } + } + + private void ParseMessage() + { + // If this is a channel message. + if(status >= (int)ChannelCommand.NoteOff && + status <= (int)ChannelCommand.PitchWheel + + ChannelMessage.MidiChannelMaxValue) + { + ParseChannelMessage(); + } + // Else if this is a meta message. + else if(status == 0xFF) + { + ParseMetaMessage(); + } + // Else if this is the start of a system exclusive message. + else if(status == (int)SysExType.Start) + { + ParseSysExMessageStart(); + } + // Else if this is a continuation of a system exclusive message. + else if(status == (int)SysExType.Continuation) + { + ParseSysExMessageContinue(); + } + // Else if this is a system common message. + else if(status >= (int)SysCommonType.MidiTimeCode && + status <= (int)SysCommonType.TuneRequest) + { + ParseSysCommonMessage(); + } + // Else if this is a system realtime message. + else if(status >= (int)SysRealtimeType.Clock && + status <= (int)SysRealtimeType.Reset) + { + ParseSysRealtimeMessage(); + } + } + + private void ParseChannelMessage() + { + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + cmBuilder.Command = ChannelMessage.UnpackCommand(status); + cmBuilder.MidiChannel = ChannelMessage.UnpackMidiChannel(status); + cmBuilder.Data1 = trackData[trackIndex]; + + trackIndex++; + + if(ChannelMessage.DataBytesPerType(cmBuilder.Command) == 2) + { + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + cmBuilder.Data2 = trackData[trackIndex]; + + trackIndex++; + } + + cmBuilder.Build(); + newTrack.Insert(ticks, cmBuilder.Result); + runningStatus = status; + } + + private void ParseMetaMessage() + { + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + MetaType type = (MetaType)trackData[trackIndex]; + + trackIndex++; + + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + if(type == MetaType.EndOfTrack) + { + newTrack.EndOfTrackOffset = ticks - previousTicks; + + trackIndex++; + } + else + { + byte[] data = new byte[ReadVariableLengthValue()]; + Array.Copy(trackData, trackIndex, data, 0, data.Length); + newTrack.Insert(ticks, new MetaMessage(type, data)); + + trackIndex += data.Length; + } + } + + private void ParseSysExMessageStart() + { + // System exclusive cancels running status. + runningStatus = 0; + + byte[] data = new byte[ReadVariableLengthValue() + 1]; + data[0] = (byte)SysExType.Start; + + Array.Copy(trackData, trackIndex, data, 1, data.Length - 1); + newTrack.Insert(ticks, new SysExMessage(data)); + + trackIndex += data.Length - 1; + } + + private void ParseSysExMessageContinue() + { + trackIndex++; + + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + // System exclusive cancels running status. + runningStatus = 0; + + // If this is an escaped message rather than a system exclusive + // continuation message. + if((trackData[trackIndex] & 0x80) == 0x80) + { + status = trackData[trackIndex]; + trackIndex++; + + ParseMessage(); + } + else + { + byte[] data = new byte[ReadVariableLengthValue() + 1]; + data[0] = (byte)SysExType.Continuation; + + Array.Copy(trackData, trackIndex, data, 1, data.Length - 1); + newTrack.Insert(ticks, new SysExMessage(data)); + + trackIndex += data.Length - 1; + } + } + + private void ParseSysCommonMessage() + { + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + // System common cancels running status. + runningStatus = 0; + + scBuilder.Type = (SysCommonType)status; + + switch((SysCommonType)status) + { + case SysCommonType.MidiTimeCode: + scBuilder.Data1 = trackData[trackIndex]; + trackIndex++; + break; + + case SysCommonType.SongPositionPointer: + scBuilder.Data1 = trackData[trackIndex]; + trackIndex++; + + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + scBuilder.Data2 = trackData[trackIndex]; + trackIndex++; + break; + + case SysCommonType.SongSelect: + scBuilder.Data1 = trackData[trackIndex]; + trackIndex++; + break; + + case SysCommonType.TuneRequest: + // Nothing to do here. + break; + } + + scBuilder.Build(); + + newTrack.Insert(ticks, scBuilder.Result); + } + + private void ParseSysRealtimeMessage() + { + SysRealtimeMessage e = null; + + switch((SysRealtimeType)status) + { + case SysRealtimeType.ActiveSense: + e = SysRealtimeMessage.ActiveSenseMessage; + break; + + case SysRealtimeType.Clock: + e = SysRealtimeMessage.ClockMessage; + break; + + case SysRealtimeType.Continue: + e = SysRealtimeMessage.ContinueMessage; + break; + + case SysRealtimeType.Reset: + e = SysRealtimeMessage.ResetMessage; + break; + + case SysRealtimeType.Start: + e = SysRealtimeMessage.StartMessage; + break; + + case SysRealtimeType.Stop: + e = SysRealtimeMessage.StopMessage; + break; + + case SysRealtimeType.Tick: + e = SysRealtimeMessage.TickMessage; + break; + } + + newTrack.Insert(ticks, e); + } + + private int ReadVariableLengthValue() + { + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + int result = 0; + + result = trackData[trackIndex]; + + trackIndex++; + + if((result & 0x80) == 0x80) + { + result &= 0x7F; + + int temp; + + do + { + if(trackIndex >= trackData.Length) + { + throw new MidiFileException("End of track unexpectedly reached."); + } + + temp = trackData[trackIndex]; + trackIndex++; + result <<= 7; + result |= temp & 0x7F; + }while((temp & 0x80) == 0x80); + } + + return result; + } + + public Track Track + { + get + { + return track; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/TrackWriter.cs b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/TrackWriter.cs new file mode 100644 index 0000000..ddb1fc9 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/Sequencing/Track Classes/TrackWriter.cs @@ -0,0 +1,247 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.IO; + +namespace Sanford.Multimedia.Midi +{ + /// + /// Writes a Track to a Stream. + /// + internal class TrackWriter + { + private static readonly byte[] TrackHeader = + { + (byte)'M', + (byte)'T', + (byte)'r', + (byte)'k' + }; + + // The Track to write to the Stream. + private Track track = new Track(); + + // The Stream to write to. + private Stream stream; + + // Running status. + private int runningStatus = 0; + + // The Track data in raw bytes. + private List trackData = new List(); + + public void Write(Stream strm) + { + this.stream = strm; + + trackData.Clear(); + + stream.Write(TrackHeader, 0, TrackHeader.Length); + + foreach(MidiEvent e in track.Iterator()) + { + WriteVariableLengthValue(e.DeltaTicks); + + switch(e.MidiMessage.MessageType) + { + case MessageType.Channel: + Write((ChannelMessage)e.MidiMessage); + break; + + case MessageType.SystemExclusive: + Write((SysExMessage)e.MidiMessage); + break; + + case MessageType.Meta: + Write((MetaMessage)e.MidiMessage); + break; + + case MessageType.SystemCommon: + Write((SysCommonMessage)e.MidiMessage); + break; + + case MessageType.SystemRealtime: + Write((SysRealtimeMessage)e.MidiMessage); + break; + } + } + + byte[] trackLength = BitConverter.GetBytes(trackData.Count); + + if(BitConverter.IsLittleEndian) + { + Array.Reverse(trackLength); + } + + stream.Write(trackLength, 0, trackLength.Length); + + foreach(byte b in trackData) + { + stream.WriteByte(b); + } + } + + private void WriteVariableLengthValue(int value) + { + int v = value; + byte[] array = new byte[4]; + int count = 0; + + array[0] = (byte)(v & 0x7F); + + v >>= 7; + + while(v > 0) + { + count++; + array[count] = (byte)((v & 0x7F) | 0x80); + v >>= 7; + } + + while(count >= 0) + { + trackData.Add(array[count]); + count--; + } + } + + private void Write(ChannelMessage message) + { + if(runningStatus != message.Status) + { + trackData.Add((byte)message.Status); + runningStatus = message.Status; + } + + trackData.Add((byte)message.Data1); + + if(ChannelMessage.DataBytesPerType(message.Command) == 2) + { + trackData.Add((byte)message.Data2); + } + } + + private void Write(SysExMessage message) + { + // System exclusive message cancel running status. + runningStatus = 0; + + trackData.Add((byte)message.Status); + + WriteVariableLengthValue(message.Length - 1); + + for(int i = 1; i < message.Length; i++) + { + trackData.Add(message[i]); + } + } + + private void Write(MetaMessage message) + { + trackData.Add((byte)message.Status); + trackData.Add((byte)message.MetaType); + + WriteVariableLengthValue(message.Length); + + trackData.AddRange(message.GetBytes()); + } + + private void Write(SysCommonMessage message) + { + // Escaped messages cancel running status. + runningStatus = 0; + + // Escaped message. + trackData.Add((byte)0xF7); + + trackData.Add((byte)message.Status); + + switch(message.SysCommonType) + { + case SysCommonType.MidiTimeCode: + trackData.Add((byte)message.Data1); + break; + + case SysCommonType.SongPositionPointer: + trackData.Add((byte)message.Data1); + trackData.Add((byte)message.Data2); + break; + + case SysCommonType.SongSelect: + trackData.Add((byte)message.Data1); + break; + } + } + + private void Write(SysRealtimeMessage message) + { + // Escaped messages cancel running status. + runningStatus = 0; + + // Escaped message. + trackData.Add((byte)0xF7); + + trackData.Add((byte)message.Status); + } + + /// + /// Gets or sets the Track to write to the Stream. + /// + public Track Track + { + get + { + return track; + } + set + { + #region Require + + if(value == null) + { + throw new ArgumentNullException("Track"); + } + + #endregion + + runningStatus = 0; + trackData.Clear(); + + track = value; + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.Designer.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.Designer.cs new file mode 100644 index 0000000..e3727d7 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.Designer.cs @@ -0,0 +1,135 @@ +namespace Sanford.Multimedia.Midi.UI +{ + partial class DeviceDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.inputComboBox = new System.Windows.Forms.ComboBox(); + this.outputComboBox = new System.Windows.Forms.ComboBox(); + this.inputLabel = new System.Windows.Forms.Label(); + this.outputLabel = new System.Windows.Forms.Label(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.AccessibleDescription = "The okay button."; + this.okButton.AccessibleName = "Okay"; + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(12, 122); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // cancelButton + // + this.cancelButton.AccessibleDescription = "The cancel button"; + this.cancelButton.AccessibleName = "Cancel"; + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(123, 122); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // inputComboBox + // + this.inputComboBox.AccessibleDescription = "Chooses the MIDI input device to use for receiving sample dump standard messages." + + ""; + this.inputComboBox.AccessibleName = "MIDI Device Input"; + this.inputComboBox.AccessibleRole = System.Windows.Forms.AccessibleRole.DropList; + this.inputComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.inputComboBox.FormattingEnabled = true; + this.inputComboBox.Location = new System.Drawing.Point(12, 25); + this.inputComboBox.Name = "inputComboBox"; + this.inputComboBox.Size = new System.Drawing.Size(186, 21); + this.inputComboBox.TabIndex = 2; + // + // outputComboBox + // + this.outputComboBox.AccessibleDescription = "Chooses the MIDI output device to use for sending sample dump standard messages."; + this.outputComboBox.AccessibleName = "MIDI Device Output"; + this.outputComboBox.AccessibleRole = System.Windows.Forms.AccessibleRole.DropList; + this.outputComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.outputComboBox.FormattingEnabled = true; + this.outputComboBox.Location = new System.Drawing.Point(12, 72); + this.outputComboBox.Name = "outputComboBox"; + this.outputComboBox.Size = new System.Drawing.Size(186, 21); + this.outputComboBox.TabIndex = 3; + // + // inputLabel + // + this.inputLabel.AutoSize = true; + this.inputLabel.Location = new System.Drawing.Point(83, 9); + this.inputLabel.Name = "inputLabel"; + this.inputLabel.Size = new System.Drawing.Size(31, 13); + this.inputLabel.TabIndex = 4; + this.inputLabel.Text = "Input"; + // + // outputLabel + // + this.outputLabel.AutoSize = true; + this.outputLabel.Location = new System.Drawing.Point(83, 56); + this.outputLabel.Name = "outputLabel"; + this.outputLabel.Size = new System.Drawing.Size(39, 13); + this.outputLabel.TabIndex = 5; + this.outputLabel.Text = "Output"; + // + // DeviceDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(217, 157); + this.Controls.Add(this.outputLabel); + this.Controls.Add(this.inputLabel); + this.Controls.Add(this.outputComboBox); + this.Controls.Add(this.inputComboBox); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Name = "DeviceDialog"; + this.Text = "MIDI Devices"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.ComboBox inputComboBox; + private System.Windows.Forms.ComboBox outputComboBox; + private System.Windows.Forms.Label inputLabel; + private System.Windows.Forms.Label outputLabel; + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.cs new file mode 100644 index 0000000..6629e4b --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.cs @@ -0,0 +1,145 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace Sanford.Multimedia.Midi.UI +{ + public partial class DeviceDialog : Form + { + private int inputDeviceID = 0; + + private int outputDeviceID = 0; + + public DeviceDialog() + { + InitializeComponent(); + + if(InputDevice.DeviceCount > 0) + { + for(int i = 0; i < InputDevice.DeviceCount; i++) + { + inputComboBox.Items.Add(InputDevice.GetDeviceCapabilities(i).name); + } + + inputComboBox.SelectedIndex = inputDeviceID; + } + + if(OutputDevice.DeviceCount > 0) + { + for(int i = 0; i < OutputDevice.DeviceCount; i++) + { + outputComboBox.Items.Add(OutputDevice.GetDeviceCapabilities(i).name); + } + + outputComboBox.SelectedIndex = inputDeviceID; + } + } + + protected override void OnShown(EventArgs e) + { + if(InputDevice.DeviceCount > 0) + { + inputComboBox.SelectedIndex = inputDeviceID; + } + + if(OutputDevice.DeviceCount > 0) + { + outputComboBox.SelectedIndex = outputDeviceID; + } + + base.OnShown(e); + } + + private void okButton_Click(object sender, EventArgs e) + { + if(InputDevice.DeviceCount > 0) + { + inputDeviceID = inputComboBox.SelectedIndex; + } + + if(OutputDevice.DeviceCount > 0) + { + outputDeviceID = outputComboBox.SelectedIndex; + } + + DialogResult = DialogResult.OK; + } + + private void cancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + } + + public int InputDeviceID + { + get + { + #region Require + + if(InputDevice.DeviceCount == 0) + { + throw new InvalidOperationException(); + } + + #endregion + + return inputDeviceID; + } + } + + public int OutputDeviceID + { + get + { + #region Require + + if(OutputDevice.DeviceCount == 0) + { + throw new InvalidOperationException(); + } + + #endregion + + return outputDeviceID; + } + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.resx b/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/DeviceDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.Designer.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.Designer.cs new file mode 100644 index 0000000..0a61bda --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.Designer.cs @@ -0,0 +1,107 @@ +namespace Sanford.Multimedia.Midi.UI +{ + partial class InputDeviceDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.inputLabel = new System.Windows.Forms.Label(); + this.inputComboBox = new System.Windows.Forms.ComboBox(); + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // inputLabel + // + this.inputLabel.AutoSize = true; + this.inputLabel.Location = new System.Drawing.Point(84, 9); + this.inputLabel.Name = "inputLabel"; + this.inputLabel.Size = new System.Drawing.Size(31, 13); + this.inputLabel.TabIndex = 13; + this.inputLabel.Text = "Input"; + // + // inputComboBox + // + this.inputComboBox.AccessibleDescription = "Chooses the MIDI input device to use for sending sample dump standard messages."; + this.inputComboBox.AccessibleName = "MIDI Device Input"; + this.inputComboBox.AccessibleRole = System.Windows.Forms.AccessibleRole.DropList; + this.inputComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.inputComboBox.FormattingEnabled = true; + this.inputComboBox.Location = new System.Drawing.Point(13, 25); + this.inputComboBox.Name = "inputComboBox"; + this.inputComboBox.Size = new System.Drawing.Size(186, 21); + this.inputComboBox.TabIndex = 12; + // + // cancelButton + // + this.cancelButton.AccessibleDescription = "The cancel button"; + this.cancelButton.AccessibleName = "Cancel"; + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(124, 75); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 11; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // okButton + // + this.okButton.AccessibleDescription = "The okay button."; + this.okButton.AccessibleName = "Okay"; + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(13, 75); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 10; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // InputDeviceDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(218, 114); + this.Controls.Add(this.inputLabel); + this.Controls.Add(this.inputComboBox); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Name = "InputDeviceDialog"; + this.Text = "MIDI Input Device"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label inputLabel; + private System.Windows.Forms.ComboBox inputComboBox; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.cs new file mode 100644 index 0000000..2a076a6 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.cs @@ -0,0 +1,105 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Windows.Forms; + +namespace Sanford.Multimedia.Midi.UI +{ + public partial class InputDeviceDialog : Form + { + private int inputDeviceID = 0; + + public InputDeviceDialog() + { + InitializeComponent(); + + if(InputDevice.DeviceCount > 0) + { + for(int i = 0; i < InputDevice.DeviceCount; i++) + { + inputComboBox.Items.Add(InputDevice.GetDeviceCapabilities(i).name); + } + + inputComboBox.SelectedIndex = inputDeviceID; + } + } + + protected override void OnShown(EventArgs e) + { + if(InputDevice.DeviceCount > 0) + { + inputComboBox.SelectedIndex = inputDeviceID; + } + + base.OnShown(e); + } + + private void okButton_Click(object sender, EventArgs e) + { + if(InputDevice.DeviceCount > 0) + { + inputDeviceID = inputComboBox.SelectedIndex; + } + + DialogResult = DialogResult.OK; + } + + private void cancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + } + + public int InputDeviceID + { + get + { + #region Require + + if(InputDevice.DeviceCount == 0) + { + throw new InvalidOperationException(); + } + + #endregion + + return inputDeviceID; + } + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.resx b/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/InputDeviceDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.Designer.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.Designer.cs new file mode 100644 index 0000000..c141c35 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.Designer.cs @@ -0,0 +1,107 @@ +namespace Sanford.Multimedia.Midi.UI +{ + partial class OutputDeviceDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.outputLabel = new System.Windows.Forms.Label(); + this.outputComboBox = new System.Windows.Forms.ComboBox(); + this.cancelButton = new System.Windows.Forms.Button(); + this.okButton = new System.Windows.Forms.Button(); + this.SuspendLayout(); + // + // outputLabel + // + this.outputLabel.AutoSize = true; + this.outputLabel.Location = new System.Drawing.Point(84, 9); + this.outputLabel.Name = "outputLabel"; + this.outputLabel.Size = new System.Drawing.Size(39, 13); + this.outputLabel.TabIndex = 9; + this.outputLabel.Text = "Output"; + // + // outputComboBox + // + this.outputComboBox.AccessibleDescription = "Chooses the MIDI output device to use for sending sample dump standard messages."; + this.outputComboBox.AccessibleName = "MIDI Device Output"; + this.outputComboBox.AccessibleRole = System.Windows.Forms.AccessibleRole.DropList; + this.outputComboBox.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList; + this.outputComboBox.FormattingEnabled = true; + this.outputComboBox.Location = new System.Drawing.Point(13, 25); + this.outputComboBox.Name = "outputComboBox"; + this.outputComboBox.Size = new System.Drawing.Size(186, 21); + this.outputComboBox.TabIndex = 8; + // + // cancelButton + // + this.cancelButton.AccessibleDescription = "The cancel button"; + this.cancelButton.AccessibleName = "Cancel"; + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(124, 75); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 7; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // okButton + // + this.okButton.AccessibleDescription = "The okay button."; + this.okButton.AccessibleName = "Okay"; + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(13, 75); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 6; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // OutputDeviceDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(218, 114); + this.Controls.Add(this.outputLabel); + this.Controls.Add(this.outputComboBox); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Name = "OutputDeviceDialog"; + this.Text = "MIDI Output Device"; + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Label outputLabel; + private System.Windows.Forms.ComboBox outputComboBox; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.Button okButton; + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.cs new file mode 100644 index 0000000..aa95f2f --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.cs @@ -0,0 +1,105 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Windows.Forms; + +namespace Sanford.Multimedia.Midi.UI +{ + public partial class OutputDeviceDialog : Form + { + private int outputDeviceID = 0; + + public OutputDeviceDialog() + { + InitializeComponent(); + + if(OutputDevice.DeviceCount > 0) + { + for(int i = 0; i < OutputDevice.DeviceCount; i++) + { + outputComboBox.Items.Add(OutputDevice.GetDeviceCapabilities(i).name); + } + + outputComboBox.SelectedIndex = outputDeviceID; + } + } + + protected override void OnShown(EventArgs e) + { + if(OutputDevice.DeviceCount > 0) + { + outputComboBox.SelectedIndex = outputDeviceID; + } + + base.OnShown(e); + } + + private void okButton_Click(object sender, EventArgs e) + { + if(OutputDevice.DeviceCount > 0) + { + outputDeviceID = outputComboBox.SelectedIndex; + } + + DialogResult = DialogResult.OK; + } + + private void cancelButton_Click(object sender, EventArgs e) + { + DialogResult = DialogResult.Cancel; + } + + public int OutputDeviceID + { + get + { + #region Require + + if(OutputDevice.DeviceCount == 0) + { + throw new InvalidOperationException(); + } + + #endregion + + return outputDeviceID; + } + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.resx b/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/OutputDeviceDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControl.PianoKey.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControl.PianoKey.cs new file mode 100644 index 0000000..4b297cf --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControl.PianoKey.cs @@ -0,0 +1,240 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Windows.Forms; +using Sanford.Multimedia; + +namespace Sanford.Multimedia.Midi.UI +{ + public partial class PianoControl + { + private class PianoKey : Control + { + private PianoControl owner; + + private bool on = false; + + private SolidBrush onBrush = new SolidBrush(Color.SkyBlue); + + private SolidBrush offBrush = new SolidBrush(Color.White); + + private int noteID = 60; + + public PianoKey(PianoControl owner) + { + this.owner = owner; + this.TabStop = false; + } + + public void PressPianoKey() + { + #region Guard + + if(on) + { + return; + } + + #endregion + + on = true; + + Invalidate(); + + owner.OnPianoKeyDown(new PianoKeyEventArgs(noteID)); + } + + public void ReleasePianoKey() + { + #region Guard + + if(!on) + { + return; + } + + #endregion + + on = false; + + Invalidate(); + + owner.OnPianoKeyUp(new PianoKeyEventArgs(noteID)); + } + + protected override void Dispose(bool disposing) + { + if(disposing) + { + onBrush.Dispose(); + offBrush.Dispose(); + } + + base.Dispose(disposing); + } + + protected override void OnMouseEnter(EventArgs e) + { + if(Control.MouseButtons == MouseButtons.Left) + { + PressPianoKey(); + } + + base.OnMouseEnter(e); + } + + protected override void OnMouseLeave(EventArgs e) + { + if(on) + { + ReleasePianoKey(); + } + + base.OnMouseLeave(e); + } + + protected override void OnMouseDown(MouseEventArgs e) + { + PressPianoKey(); + + if(!owner.Focused) + { + owner.Focus(); + } + + base.OnMouseDown(e); + } + + protected override void OnMouseUp(MouseEventArgs e) + { + ReleasePianoKey(); + + base.OnMouseUp(e); + } + + protected override void OnMouseMove(MouseEventArgs e) + { + if(e.X < 0 || e.X > Width || e.Y < 0 || e.Y > Height) + { + Capture = false; + } + + base.OnMouseMove(e); + } + + protected override void OnPaint(PaintEventArgs e) + { + if(on) + { + e.Graphics.FillRectangle(onBrush, 0, 0, Size.Width, Size.Height); + } + else + { + e.Graphics.FillRectangle(offBrush, 0, 0, Size.Width, Size.Height); + } + + e.Graphics.DrawRectangle(Pens.Black, 0, 0, Size.Width - 1, Size.Height - 1); + + base.OnPaint(e); + } + + public Color NoteOnColor + { + get + { + return onBrush.Color; + } + set + { + onBrush.Color = value; + + if(on) + { + Invalidate(); + } + } + } + + public Color NoteOffColor + { + get + { + return offBrush.Color; + } + set + { + offBrush.Color = value; + + if(!on) + { + Invalidate(); + } + } + } + + public int NoteID + { + get + { + return noteID; + } + set + { + #region Require + + if(value < 0 || value > ShortMessage.DataMaxValue) + { + throw new ArgumentOutOfRangeException("NoteID", noteID, + "Note ID out of range."); + } + + #endregion + + noteID = value; + } + } + + public bool IsPianoKeyPressed + { + get + { + return on; + } + } + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControl.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControl.cs new file mode 100644 index 0000000..e011145 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControl.cs @@ -0,0 +1,503 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections; +using System.Drawing; +using System.Threading; +using System.Windows.Forms; + +namespace Sanford.Multimedia.Midi.UI +{ + public partial class PianoControl : Control + { + private enum KeyType + { + White, + Black + } + + private readonly static Hashtable keyTable = new Hashtable(); + + private static readonly KeyType[] KeyTypeTable = + { + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, + KeyType.White, KeyType.Black, KeyType.White, KeyType.Black, KeyType.White, KeyType.White, KeyType.Black, KeyType.White + }; + + private delegate void NoteMessageCallback(ChannelMessage message); + + private const int DefaultLowNoteID = 21; + + private const int DefaultHighNoteID = 109; + + private const double BlackKeyScale = 0.666666666; + + private SynchronizationContext context; + + private int lowNoteID = DefaultLowNoteID; + + private int highNoteID = DefaultHighNoteID; + + private int octaveOffset = 5; + + private Color noteOnColor = Color.SkyBlue; + + private PianoKey[] keys = null; + + private int whiteKeyCount; + + private NoteMessageCallback noteOnCallback; + + private NoteMessageCallback noteOffCallback; + + public event EventHandler PianoKeyDown; + + public event EventHandler PianoKeyUp; + + static PianoControl() + { + keyTable.Add(Keys.A, 0); + keyTable.Add(Keys.W, 1); + keyTable.Add(Keys.S, 2); + keyTable.Add(Keys.E, 3); + keyTable.Add(Keys.D, 4); + keyTable.Add(Keys.F, 5); + keyTable.Add(Keys.T, 6); + keyTable.Add(Keys.G, 7); + keyTable.Add(Keys.Y, 8); + keyTable.Add(Keys.H, 9); + keyTable.Add(Keys.U, 10); + keyTable.Add(Keys.J, 11); + keyTable.Add(Keys.K, 12); + keyTable.Add(Keys.O, 13); + keyTable.Add(Keys.L, 14); + keyTable.Add(Keys.P, 15); + } + + public PianoControl() + { + CreatePianoKeys(); + InitializePianoKeys(); + + context = SynchronizationContext.Current; + + noteOnCallback = delegate(ChannelMessage message) + { + if(message.Data2 > 0) + { + keys[message.Data1 - lowNoteID].PressPianoKey(); + } + else + { + keys[message.Data1 - lowNoteID].ReleasePianoKey(); + } + }; + + noteOffCallback = delegate(ChannelMessage message) + { + keys[message.Data1 - lowNoteID].ReleasePianoKey(); + }; + } + + private void CreatePianoKeys() + { + // If piano keys have already been created. + if(keys != null) + { + // Remove and dispose of current piano keys. + foreach(PianoKey key in keys) + { + Controls.Remove(key); + key.Dispose(); + } + } + + keys = new PianoKey[HighNoteID - LowNoteID]; + + whiteKeyCount = 0; + + for(int i = 0; i < keys.Length; i++) + { + keys[i] = new PianoKey(this); + keys[i].NoteID = i + LowNoteID; + + if(KeyTypeTable[keys[i].NoteID] == KeyType.White) + { + whiteKeyCount++; + } + else + { + keys[i].NoteOffColor = Color.Black; + keys[i].BringToFront(); + } + + keys[i].NoteOnColor = NoteOnColor; + + Controls.Add(keys[i]); + } + } + + private void InitializePianoKeys() + { + #region Guard + + if(keys.Length == 0) + { + return; + } + + #endregion + + int whiteKeyWidth = Width / whiteKeyCount; + int blackKeyWidth = (int)(whiteKeyWidth * BlackKeyScale); + int blackKeyHeight = (int)(Height * BlackKeyScale); + int offset = whiteKeyWidth - blackKeyWidth / 2; + int n = 0; + int w = 0; + + while(n < keys.Length) + { + if(KeyTypeTable[keys[n].NoteID] == KeyType.White) + { + keys[n].Height = Height; + keys[n].Width = whiteKeyWidth; + keys[n].Location = new Point(w * whiteKeyWidth, 0); + w++; + n++; + } + else + { + keys[n].Height = blackKeyHeight; + keys[n].Width = blackKeyWidth; + keys[n].Location = new Point(offset + (w - 1) * whiteKeyWidth); + keys[n].BringToFront(); + n++; + } + } + } + + public void Send(ChannelMessage message) + { + if(message.Command == ChannelCommand.NoteOn && + message.Data1 >= LowNoteID && message.Data1 <= HighNoteID) + { + if(InvokeRequired) + { + BeginInvoke(noteOnCallback, message); + } + else + { + noteOnCallback(message); + } + } + else if(message.Command == ChannelCommand.NoteOff && + message.Data1 >= LowNoteID && message.Data1 <= HighNoteID) + { + if(InvokeRequired) + { + BeginInvoke(noteOffCallback, message); + } + else + { + noteOffCallback(message); + } + } + } + + public void PressPianoKey(int noteID) + { + #region Require + + if(noteID < lowNoteID || noteID > highNoteID) + { + throw new ArgumentOutOfRangeException(); + } + + #endregion + + keys[noteID - lowNoteID].PressPianoKey(); + } + + public void ReleasePianoKey(int noteID) + { + #region Require + + if(noteID < lowNoteID || noteID > highNoteID) + { + throw new ArgumentOutOfRangeException(); + } + + #endregion + + keys[noteID - lowNoteID].ReleasePianoKey(); + } + + public void PressPianoKey(Keys k) + { + if(!Focused) + { + return; + } + + if(keyTable.Contains(k)) + { + int noteID = (int)keyTable[k] + 12 * octaveOffset; + + if(noteID >= LowNoteID && noteID <= HighNoteID) + { + if(!keys[noteID - lowNoteID].IsPianoKeyPressed) + { + keys[noteID - lowNoteID].PressPianoKey(); + } + } + } + else + { + if(k == Keys.D0) + { + octaveOffset = 0; + } + else if(k == Keys.D1) + { + octaveOffset = 1; + } + else if(k == Keys.D2) + { + octaveOffset = 2; + } + else if(k == Keys.D3) + { + octaveOffset = 3; + } + else if(k == Keys.D4) + { + octaveOffset = 4; + } + else if(k == Keys.D5) + { + octaveOffset = 5; + } + else if(k == Keys.D6) + { + octaveOffset = 6; + } + else if(k == Keys.D7) + { + octaveOffset = 7; + } + else if(k == Keys.D8) + { + octaveOffset = 8; + } + else if(k == Keys.D9) + { + octaveOffset = 9; + } + } + } + + public void ReleasePianoKey(Keys k) + { + #region Guard + + if(!keyTable.Contains(k)) + { + return; + } + + #endregion + + int noteID = (int)keyTable[k] + 12 * octaveOffset; + + if(noteID >= LowNoteID && noteID <= HighNoteID) + { + keys[noteID - lowNoteID].ReleasePianoKey(); + } + } + + protected override void OnResize(EventArgs e) + { + InitializePianoKeys(); + + base.OnResize(e); + } + + protected override void Dispose(bool disposing) + { + if(disposing) + { + foreach(PianoKey key in keys) + { + key.Dispose(); + } + } + + base.Dispose(disposing); + } + + protected virtual void OnPianoKeyDown(PianoKeyEventArgs e) + { + EventHandler handler = PianoKeyDown; + + if(handler != null) + { + handler(this, e); + } + } + + protected virtual void OnPianoKeyUp(PianoKeyEventArgs e) + { + EventHandler handler = PianoKeyUp; + + if(handler != null) + { + handler(this, e); + } + } + + public int LowNoteID + { + get + { + return lowNoteID; + } + set + { + #region Require + + if(value < 0 || value > ShortMessage.DataMaxValue) + { + throw new ArgumentOutOfRangeException("LowNoteID", value, + "Low note ID out of range."); + } + + #endregion + + #region Guard + + if(value == lowNoteID) + { + return; + } + + #endregion + + lowNoteID = value; + + if(lowNoteID > highNoteID) + { + highNoteID = lowNoteID; + } + + CreatePianoKeys(); + InitializePianoKeys(); + } + } + + public int HighNoteID + { + get + { + return highNoteID; + } + set + { + #region Require + + if(value < 0 || value > ShortMessage.DataMaxValue) + { + throw new ArgumentOutOfRangeException("HighNoteID", value, + "High note ID out of range."); + } + + #endregion + + #region Guard + + if(value == highNoteID) + { + return; + } + + #endregion + + highNoteID = value; + + if(highNoteID < lowNoteID) + { + lowNoteID = highNoteID; + } + + CreatePianoKeys(); + InitializePianoKeys(); + } + } + + public Color NoteOnColor + { + get + { + return noteOnColor; + } + set + { + #region Guard + + if(value == noteOnColor) + { + return; + } + + #endregion + + noteOnColor = value; + + foreach(PianoKey key in keys) + { + key.NoteOnColor = noteOnColor; + } + } + } + } +} diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.Designer.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.Designer.cs new file mode 100644 index 0000000..649ae63 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.Designer.cs @@ -0,0 +1,163 @@ +namespace Sanford.Multimedia.Midi.UI +{ + partial class PianoControlDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if(disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.okButton = new System.Windows.Forms.Button(); + this.cancelButton = new System.Windows.Forms.Button(); + this.lowNoteIDNumericUpDown = new System.Windows.Forms.NumericUpDown(); + this.noteRangeGroupBox = new System.Windows.Forms.GroupBox(); + this.highNoteIDLabel = new System.Windows.Forms.Label(); + this.lowNoteIDLabel = new System.Windows.Forms.Label(); + this.highNoteIDNumericUpDown = new System.Windows.Forms.NumericUpDown(); + ((System.ComponentModel.ISupportInitialize)(this.lowNoteIDNumericUpDown)).BeginInit(); + this.noteRangeGroupBox.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)(this.highNoteIDNumericUpDown)).BeginInit(); + this.SuspendLayout(); + // + // okButton + // + this.okButton.DialogResult = System.Windows.Forms.DialogResult.OK; + this.okButton.Location = new System.Drawing.Point(18, 117); + this.okButton.Name = "okButton"; + this.okButton.Size = new System.Drawing.Size(75, 23); + this.okButton.TabIndex = 0; + this.okButton.Text = "OK"; + this.okButton.UseVisualStyleBackColor = true; + this.okButton.Click += new System.EventHandler(this.okButton_Click); + // + // cancelButton + // + this.cancelButton.DialogResult = System.Windows.Forms.DialogResult.Cancel; + this.cancelButton.Location = new System.Drawing.Point(142, 117); + this.cancelButton.Name = "cancelButton"; + this.cancelButton.Size = new System.Drawing.Size(75, 23); + this.cancelButton.TabIndex = 1; + this.cancelButton.Text = "Cancel"; + this.cancelButton.UseVisualStyleBackColor = true; + this.cancelButton.Click += new System.EventHandler(this.cancelButton_Click); + // + // lowNoteIDNumericUpDown + // + this.lowNoteIDNumericUpDown.Location = new System.Drawing.Point(6, 43); + this.lowNoteIDNumericUpDown.Maximum = new decimal(new int[] { + 127, + 0, + 0, + 0}); + this.lowNoteIDNumericUpDown.Name = "lowNoteIDNumericUpDown"; + this.lowNoteIDNumericUpDown.Size = new System.Drawing.Size(60, 20); + this.lowNoteIDNumericUpDown.TabIndex = 4; + this.lowNoteIDNumericUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.lowNoteIDNumericUpDown.Value = new decimal(new int[] { + 21, + 0, + 0, + 0}); + this.lowNoteIDNumericUpDown.ValueChanged += new System.EventHandler(this.lowNoteIDNumericUpDown_ValueChanged); + // + // noteRangeGroupBox + // + this.noteRangeGroupBox.Controls.Add(this.highNoteIDLabel); + this.noteRangeGroupBox.Controls.Add(this.lowNoteIDLabel); + this.noteRangeGroupBox.Controls.Add(this.highNoteIDNumericUpDown); + this.noteRangeGroupBox.Controls.Add(this.lowNoteIDNumericUpDown); + this.noteRangeGroupBox.Location = new System.Drawing.Point(12, 12); + this.noteRangeGroupBox.Name = "noteRangeGroupBox"; + this.noteRangeGroupBox.Size = new System.Drawing.Size(211, 88); + this.noteRangeGroupBox.TabIndex = 5; + this.noteRangeGroupBox.TabStop = false; + this.noteRangeGroupBox.Text = "Note Range"; + // + // highNoteIDLabel + // + this.highNoteIDLabel.AutoSize = true; + this.highNoteIDLabel.Location = new System.Drawing.Point(150, 27); + this.highNoteIDLabel.Name = "highNoteIDLabel"; + this.highNoteIDLabel.Size = new System.Drawing.Size(55, 13); + this.highNoteIDLabel.TabIndex = 7; + this.highNoteIDLabel.Text = "High Note"; + // + // lowNoteIDLabel + // + this.lowNoteIDLabel.AutoSize = true; + this.lowNoteIDLabel.Location = new System.Drawing.Point(12, 27); + this.lowNoteIDLabel.Name = "lowNoteIDLabel"; + this.lowNoteIDLabel.Size = new System.Drawing.Size(53, 13); + this.lowNoteIDLabel.TabIndex = 6; + this.lowNoteIDLabel.Text = "Low Note"; + // + // highNoteIDNumericUpDown + // + this.highNoteIDNumericUpDown.Location = new System.Drawing.Point(145, 43); + this.highNoteIDNumericUpDown.Maximum = new decimal(new int[] { + 127, + 0, + 0, + 0}); + this.highNoteIDNumericUpDown.Name = "highNoteIDNumericUpDown"; + this.highNoteIDNumericUpDown.Size = new System.Drawing.Size(60, 20); + this.highNoteIDNumericUpDown.TabIndex = 5; + this.highNoteIDNumericUpDown.TextAlign = System.Windows.Forms.HorizontalAlignment.Right; + this.highNoteIDNumericUpDown.Value = new decimal(new int[] { + 109, + 0, + 0, + 0}); + this.highNoteIDNumericUpDown.ValueChanged += new System.EventHandler(this.highNoteIDNumericUpDown_ValueChanged); + // + // PianoControlDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(235, 155); + this.Controls.Add(this.noteRangeGroupBox); + this.Controls.Add(this.cancelButton); + this.Controls.Add(this.okButton); + this.Name = "PianoControlDialog"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "Piano Control Settings"; + ((System.ComponentModel.ISupportInitialize)(this.lowNoteIDNumericUpDown)).EndInit(); + this.noteRangeGroupBox.ResumeLayout(false); + this.noteRangeGroupBox.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)(this.highNoteIDNumericUpDown)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button okButton; + private System.Windows.Forms.Button cancelButton; + private System.Windows.Forms.NumericUpDown lowNoteIDNumericUpDown; + private System.Windows.Forms.GroupBox noteRangeGroupBox; + private System.Windows.Forms.Label highNoteIDLabel; + private System.Windows.Forms.Label lowNoteIDLabel; + private System.Windows.Forms.NumericUpDown highNoteIDNumericUpDown; + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.cs new file mode 100644 index 0000000..948c034 --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.cs @@ -0,0 +1,152 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Text; +using System.Windows.Forms; + +namespace Sanford.Multimedia.Midi.UI +{ + public partial class PianoControlDialog : Form + { + private int lowNoteID; + + private int highNoteID; + + public PianoControlDialog() + { + InitializeComponent(); + + UpdateProperties(); + } + + private void UpdateProperties() + { + lowNoteID = (int)lowNoteIDNumericUpDown.Value; + highNoteID = (int)highNoteIDNumericUpDown.Value; + } + + private void lowNoteIDNumericUpDown_ValueChanged(object sender, EventArgs e) + { + if(lowNoteIDNumericUpDown.Value > highNoteIDNumericUpDown.Value) + { + highNoteIDNumericUpDown.Value = lowNoteIDNumericUpDown.Value; + } + } + + private void highNoteIDNumericUpDown_ValueChanged(object sender, EventArgs e) + { + if(highNoteIDNumericUpDown.Value < lowNoteIDNumericUpDown.Value) + { + lowNoteIDNumericUpDown.Value = highNoteIDNumericUpDown.Value; + } + } + + private void okButton_Click(object sender, EventArgs e) + { + UpdateProperties(); + + Close(); + } + + private void cancelButton_Click(object sender, EventArgs e) + { + Close(); + } + + public int LowNoteID + { + get + { + return lowNoteID; + } + set + { + #region Require + + if(value < 0 || value > ShortMessage.DataMaxValue) + { + throw new ArgumentOutOfRangeException("LowNoteID", value, + "Low note ID out of range."); + } + + #endregion + + lowNoteID = value; + + lowNoteIDNumericUpDown.Value = value; + + if(lowNoteID > highNoteID) + { + highNoteID = lowNoteID; + highNoteIDNumericUpDown.Value = highNoteID; + } + } + } + + public int HighNoteID + { + get + { + return highNoteID; + } + set + { + #region Require + + if(value < 0 || value > ShortMessage.DataMaxValue) + { + throw new ArgumentOutOfRangeException("HighNoteID", value, + "High note ID out of range."); + } + + #endregion + + highNoteID = value; + + highNoteIDNumericUpDown.Value = value; + + if(highNoteID < lowNoteID) + { + lowNoteID = highNoteID; + lowNoteIDNumericUpDown.Value = highNoteID; + } + } + } + } +} \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.resx b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.resx new file mode 100644 index 0000000..19dc0dd --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoControlDialog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoKeyEventArgs.cs b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoKeyEventArgs.cs new file mode 100644 index 0000000..4bda99d --- /dev/null +++ b/DependenciesCode/Sanford.Multimedia.Midi/UI/PianoKeyEventArgs.cs @@ -0,0 +1,56 @@ +#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: jabberdabber@hotmail.com + */ + +#endregion + +using System; + +namespace Sanford.Multimedia.Midi.UI +{ + public class PianoKeyEventArgs : EventArgs + { + private int noteID; + + public PianoKeyEventArgs(int noteID) + { + this.noteID = noteID; + } + + public int NoteID + { + get + { + return noteID; + } + } + } +}