Click here to Skip to main content
Click here to Skip to main content

C# MIDI Toolkit

By , 18 Apr 2007
 

Screenshot - SequencerDemo.png

Contents

  1. Introduction
  2. Flow-based programming
    1. Implementing flow-based programming in C#
  3. MIDI messages
    1. Message builders
    2. MessageDispatcher class
  4. Clocks
  5. MidiEvent class
  6. Track class
  7. Sequence class
  8. MIDI devices
    1. InputDevice class
    2. OutputDeviceBase class
    3. OutputDevice class
    4. OutputStream class
  9. Sequencer Class
  10. Dependencies
  11. Conclusion
  12. History

Introduction

This is the fifth version of my .NET MIDI toolkit. I had thought that the previous version was the final one, but I have made many changes that have warranted a new version. This version takes a more traditional C#/.NET approach to flow-based programming, which I'll describe below. I wasn't comfortable with version four's implementation along these lines, so I took a step back and made changes that keep the flow-based approach while remaining within C#/.NET accepted idioms. I'm hoping that this will make the toolkit easier to use and understand.

The toolkit has seen many revisions over the past two to three years. Each revision has been an almost total rewrite of the previous one. When writing software, it is usually a bad idea to make updates that break software using previous versions. However, my goal in creating this toolkit has been to provide the best design possible. As I have grown as a programmer, I have improved my skills and understanding of software design. This has led me to revise the earlier designs of the toolkit without regard to how these revisions will break code. Not exactly the attitude one wants to adopt in a professional setting, but since the toolkit is free and since I have used it as a learning experience to improve my craft, my priorities are different.

top

Flow-based programming

Before I get into the specifics of the toolkit, I would like to talk about its architecture. With each version of the toolkit, I have struggled with how to structure the flow of messages through the system. I wanted an approach that would be versatile and allow customization. It would be nice, I thought, if users could plug their own classes into the flow of MIDI messages to do whatever they want. For example, say you wanted to write a harmonizer, a class capable of transposing notes in a specified key to create harmony parts automatically. It should be easy to simply plug the harmonizer into the toolkit without affecting other classes. In other words, the toolkit should be customizable and configurable.

Investigating this problem led me to J. Paul Morrison's excellent website on flow-based programming. He has written a book on the subject, which can be found on his website as well as at Amazon.

The idea is simple and will probably seem familiar to most: Data flows through a network of components. Each component can do something interesting with the data before passing it along to the next component. In design pattern terms, this approach is most like the Pipe and Filter pattern and is also similar to the Chain of Responsibility pattern. Please check out J. Paul Morrison's excellent book for more information.

(Just to be clear: when I say "component," I'm not necessarily talking about classes that implement the IComponent interface. I'm speaking in more general terms. A component is simply an object in a chain of objects designed to process the flow of messages.)

Below is a very basic network of components designed to handle the flow of MIDI channel messages:

Screenshot - MessageFlow.PNG

The flow of messages begins with the input device. An input device receives MIDI messages from an external source. Next, the messages flow through a user component. This component might want to do something like change the MIDI channel, transpose note messages, or change the messages in some way. Then the messages pass through the channel stopper. This component simply keeps track of all currently sounding notes. When the message flow stops, the channel stopper can turn off all sounding notes so that none of them hang. Finally, the messages reach the output device. Here they are sent to an external MIDI device.

top

Implementing flow-based programming in C#

Well, this is something I really struggled with. You can read a bit about the different ways I tried to achieve this by reading my blog. I found myself going round in circles on this. In version four of the toolkit, I settled on the idea of using a source/sink abstraction. I created an interface representing "Sources." A source represents a source of MIDI messages. "Sinks" were represented by delegates that could be connected to sources; a sink is simply a method capable of receiving a MIDI message. This worked well but it was a little confusing because the implementation looked a little "funny." That is to say, a C# programmer looking at the code for the first time might be confused as to what is going on.

I decided to do away with the sink/source infrastructure and use something more idiomatic. Sources of MIDI messages raise events when they have messages to send. Instead of implementing an interface and having Connect and Disconnect methods for hooking to sinks, they would simply have events. There are two advantages here: First, sources no longer have to implement an ISource interface, and second, .NET events are something very familiar to us. So sources of MIDI messages now look like your everyday class that just happens to have one or more events.

How about sinks, those objects that can receive MIDI messages? A sink can be anything. It's just an object that has a method that can receive a MIDI message. In version four, I had a Sink delegate for representing methods of objects capable of receiving MIDI messages. These delegates were used to connect with sources. This approach of using delegates to "connect" sources and sinks is still used in the toolkit but not as before. Instead, delegates are used as adaptors that connect to the events raised in sources and adapts the events so that objects that need to receive the messages can do so without any knowledge of the source.

Let's look at an example. Say that we're using an InputDevice to receive MIDI messages from a MIDI device, such as your soundcard. The InputDevice raises a ChannelMessageReceived event each time it receives a channel message. Suppose that we want to keep track of any note-on channel messages so that when we decide to stop receiving messages, we can turn off any currently sounding notes to keep them from "hanging." The ChannelStopper class is just for this purpose. However, the ChannelStopper has no knowledge of the InputDevice class. We need a way to hook them up so that messages generated by the InputDevice can be passed along to the ChannelStopper. Here is how we can do this with an anonymous method:

InputDevice inDevice = new InputDevice(0);
ChannelStopper stopper = new ChannelStopper();

inDevice.ChannelMessageReceived += delegate(object sender, ChannelMessageEventArgs e)
{
    stopper.Process(e.Message);
};

inDevice.StartRecording();

// ...

In this example, an anonymous method adapts events raised by the InputDevice so that they can be processed by a ChannelStopper. The InputDevice is the source of channel messages and the ChannelStopper is a sink capable of receiving and processing channel messages. The nice thing about this approach is that no explicit source/sink infrastructure is needed. Neither class knows anything about being a source or sink. The flow of messages is orchestrated by an external agent, in this case, an anonymous method.

top

MIDI messages

There are several categories of MIDI messages: Channel, System Exclusive, Meta, etc. In designing a MIDI toolkit, the challenge is to decide how to represent these messages. One approach is to create two or three general MIDI classes and have specific types of MIDI messages represented through the properties of those classes. The Java MIDI API takes this route. Another approach is to create a large collection of finely grained classes to represent all of the different types of MIDI messages. For example, there are many types of Channel messages such as the Note-on, Note-off, Program change, and Pitch Bend messages. The fine grained approach would create a class for each of those message types. My approach was to take a middle ground. I created classes for the general categories of MIDI messages but left the specific types of messages as properties within those classes. This kept the class hierarchy lightweight and manageable while providing enough specialization to make working with MIDI messages easy.

Here is the hierarchy of MIDI message classes in the MIDI toolkit:

  • IMidiMessage
    • ShortMessage
      • ChannelMessage
      • SysCommonMessage
      • SysRealtimeMessage
    • SysExMessage
    • MetaMessage

Specific types of messages are represented through properties. For example, the ChannelMessage class has a Command property that can be set to represent the various types of Channel messages.

top

Message builders

All message classes are immutable. This makes sharing messages throughout an application safe. To create messages, you pass the desired property values to their constructor. Additionally, the toolkit provides a set of builder classes for making message creation more convenient.

The toolkit provides the following message builders:

  • ChannelMessageBuilder
  • SysCommonMessageBuilder
  • KeySignatureBuilder
  • MetaTextBuilder
  • SongPositionPointerBuilder
  • TempoChangeBuilder
  • TimeSignatureBuilder

The ChannelMessageBuilder and the SysCommonBuilder also use the Flyweight design pattern. When a new message is built, it is stored in a cache. When another message is needed that has the exact same properties as a message that has already been built, the previous message is retrieved rather than creating a new one. When you consider that a typical MIDI sequence is made up of thousands of messages, many of them identical, it is easy to see how the Flyweight pattern is applicable.

Here is an example of creating a ChannelMessage object representing a note-on message:

ChannelMessageBuilder builder = new ChannelMessageBuilder();

builder.Command = ChannelCommand.NoteOn;
builder.MidiChannel = 0;
builder.Data1 = 60;
builder.Data2 = 127;
builder.Build();
Console.WriteLine(builder.Result);

After the builder has been initialized with the desired properties, the MIDI message is built with a call to the Build method. The MIDI message can then be retrieved via the Result property.

There are several builder classes for creating specific types of meta messages. For example, to create a key signature meta message, you use the KeySignatureBuilder class:

KeySignatureBuilder builder = new KeySignatureBuilder();
builder.Key = Key.CMajor;
builder.Build();
Console.WriteLine(builder.Result);

top

MessageDispatcher class

Often there is a need to process a collection of IMidiMessages. How each message is processed depends on its type. The problem is that you cannot tell an IMidiMessage's type without an explicit check. The IMidiMessage provides a MessageType property just for this purpose. However, having to repeatedly check message types throughout your code can be cumbersome.

The MessageDispatch class is designed to automate these checks. This class acts as a source for every type of MIDI message. It raises an event each time it dispatches a message. The type of event is determined by the type of message it is dispatching.

top

Clocks

MIDI playback is driven by ticks that occur periodically. The source of these ticks are MIDI clocks. MIDI clocks come in all shapes and sizes. For example, playback can be driven by an internal or external clock. Also, the way in which the ticks are generated depends on whether the MIDI sequence has pulses per quarter note resolution or SMPTE resolution. For the vast majority of situations, an internal clock generating ticks with pulses per quarter note resolution is all you need.

The IClock interface represents the basic functionality for all MIDI clocks:

public interface IClock
{
    event EventHandler Tick;
    event EventHandler Started;
    event EventHandler Continued;
    event EventHandler Stopped;
    bool IsRunning
    {
        get;
    }
}

The Tick event occurs when a MIDI tick has elapsed. The Started, Continued, and Stopped events are self-explanatory. However, it should be pointed out that when the Started event occurs, sequence playback starts from the beginning of the sequence. When the Continued event occurs, playback starts from the current position. The IsRunning property indicates whether the clock is running.

You may notice that there are no methods in the interface for starting and stopping a clock. That is because with clocks that are driven by an external source, the source is responsible for starting and stopping the clock. The clocks receive messages via MIDI and based on those messages starts or stops generating ticks. Since all MIDI clocks implement IClock, it only represents the functionality common to all the clocks.

At this time, the toolkit provides only one clock class, the MidiInternalClock. This clock generates MIDI ticks internally using pulses per quarter note resolution. For the majority of situations, this clock will work fine.

The MidiInternalClock has a Tempo property for setting the tempo in microseconds per beat. To set the tempo to 120 bpm, for example, you would set the Tempo property to 500000. It can receive meta tempo change messages. When a meta tempo change message is passed to it, it changes its tempo to match the tempo represented by the message.

top

MidiEvent class

A MIDI file is made up of several tracks. Each track contains one or more timestamped MIDI messages. The timestamp represents the number of ticks since the last message was played. This timestamp is called delta ticks. The MidiEvent class represents a timestamped MIDI message. It has three public properties:

  • DeltaTicks
  • AbsoluteTicks
  • MidiMessage

The DeltaTicks property represents the number of ticks since the last MidiEvent. In other words, this value represents how long to wait after playing the previous MidiEvent before playing the current MidiEvent. For example, if the DeltaTicks value is 10, we would allow 10 ticks to elapse before playing the MIDI message represented by the current MidiEvent.

The AbsoluteTicks represents the overall position of the MidiEvent. This is the total number of ticks that have elapsed until the current MidiEvent.

The MidiMessage property is the IMidiMessage represented by the MidiEvent.

In addition there are two internal properties, one which points to the previous MidiEvent in the track, and one which points to the next MidiEvent in the track. In other words, the MidiEvent class acts as a node in a doubly linked list of MidiEvents.

top

Track class

The Track class represents a collection of MidiEvents. It is responsible for maintaining a collection of MidiEvents in proper order. MidiEvents are not directly added to a Track. Instead, you add an IMidiMessage, specifying its absolute position in the Track. The Track then creates a MidiEvent to represent the message and inserts it into its collection of MidiEvents.

In addition to providing functionality for adding and removing MIDI events, the Track class also provides several iterators. There is a standard iterator that simply iterates over the MidiEvents one at a time. Another iterator takes a MessageDispatcher object and passes each IMidiMessage to the dispatcher which in turn raises an event specific to the type of message it is dispatching. The value the iterator returns is the absolute ticks for the current MidiEvent.

Perhaps the most useful iterator is the one that when advanced moves forward only one tick at a time. The iterator keeps track of its tick position in the Track. When the tick count has reached a value in which it is time to play the next MidiEvent, it passes the IMidiMessage represented by the MidiEvent to the MessageDispatcher and returns the absolute tick count. This iterator also takes a ChannelChaser object as well as a start position value and "chases" up to the start position before switching to the playback mode. In essence, this iterator allows us to stream the Track in real-time.

top

Sequence class

The Sequence class represents a collection of Tracks. It also provides functionality for loading and saving MIDI files, so Sequences can load and save themselves.

Every Sequence has a division value. This value represents the resolution of the Sequence and is represented by a property. There are two types of division values: Pulses per quarter note and SMPTE. The Sequence has a SequenceType property indicating the sequence type. Unfortunately, SMPTE sequences aren't supported at this time.

top

MIDI devices

There are several MIDI device classes in the toolkit. Each device class is derived directly or indirectly from the abstract Device class in the Sanford.Multimedia namespace. The InputDevice class represents a MIDI device capable of receiving MIDI messages from an external source, such as a MIDI keyboard controller or synthesizer. The OutputDeviceBase class is an abstract class that serves as the base class for the output device classes. The OutputDevice class represents a MIDI device capable of sending MIDI messages to an external source or your soundcard. And the OutputStream class encapsulates the Windows Multimedia MIDI output stream API. It is capable of playing back timestamped MIDI messages.

There can be more than one of these devices present on your computer. To determine the number of input devices present, for example, you would query the InputDevice's static DeviceCount property. The output device classes also have this property.

Each MIDI device has its own unique ID. This is simply an integer value representing the device's order in the list of devices available. For example, the first output device on your system would have an ID of 0. The second output device would have an ID of 1, and so on. The same is true for the input devices. When you create a MIDI device, you pass it the ID of the device you wish to use to its constructor. If there was an error in opening the device, an exception is thrown.

To find out the capabilities of a device, you query the class' static GetDeviceCapabilities method, passing it the device ID of the device you are interested in. This method will return a structure filled with values representing the capabilities of the specified MIDI device.

Let's describe each device class in detail:

top

InputDevice class

The InputDevice class represents a MIDI device capable of receiving MIDI messages. It has an event for each of the major MIDI message it can receive. To receive MIDI messages, you connect to one or more of these events. Then you call the StartRecording method. Recording will continue until either StopRecording or Reset is called. The InputDevice lets you set the size of the sysex buffer it uses to receive sysex messages. When the InputDevice has received a complete sysex message, it raises the SysExReceived event.

top

OutputDeviceBase class

The OutputDeviceBase class is an abstract class that provides basic functionality for sending MIDI messages. It has several overloaded Send methods for sending various types of MIDI messages.

top

OutputDevice class

The OutputDevice class represents a MIDI device capable of sending MIDI messages. It inherits most of its functionality from the OutputDeviceBase class. It also provides running status functionality.

The following code creates an OutputDevice, builds and sends a note-on message, sleeps for one second, and then builds and sends a note-off message:

using(OutputDevice outDevice = new OutputDevice(0))
{
    ChannelMessageBuilder builder = new ChannelMessageBuilder();

    builder.Command = ChannelCommand.NoteOn;
    builder.MidiChannel = 0;
    builder.Data1 = 60;
    builder.Data2 = 127;
    builder.Build();

    outDevice.Send(builder.Result);

    Thread.Sleep(1000);

    builder.Command = ChannelCommand.NoteOff;
    builder.Data2 = 0;
    builder.Build();

    outDevice.Send(builder.Result);
}

top

OutputStream class

The OutputStream class is also derived from the OutputDeviceBase class. It encapsulates the Windows multimedia MIDI output stream API. It provides functionality for playing back MIDI messages.

To play MIDI messages, you call StartPlaying. The OutputStream will then begin playing back any MIDI messages in its queue. To place MIDI messages in the queue, you first write one or more MidiEvents using the Write method. After writing the desired number of MidiEvents, you call Flush. This flushes the events to the stream causing it to play them back.

top

Sequencer Class

The Sequencer class is back. It's a lightweight class for playing back Sequences. I felt the previous MidiFilePlayer class was not the best means for playing back MIDI sequences. I wanted to give the toolkit the ability to play Sequences your create programmatically. One issue that caused me to shy away from a Sequencer class (after having created one in earlier versions) is the problem of a Sequence changing as it's being played by a Sequencer. I still haven't solved that problem, but I didn't want that issue to prevent easy Sequence playback. So I'm putting in a new version of the Sequencer class with the understanding that it's meant to be used for simple playback. For something more sophisticated, you can use it as the basis for creating something more.

top

Dependencies

The MIDI toolkit depends on the DelegateQueue class from my Sanford.Threading namespace; the InputDevice and OutputDevice classes use it for queueing MIDI events. In turn, the Sanford.Threading namespace depends on my Sanford.Collection namespace, so that assembly is also necessary for the toolkit to compile. Finally, the toolkit uses the Sanford.Multimedia namespace. I've provided all of the assemblies with the download. I've linked the projects that use them to the assemblies in hopes that the toolkit will compile out of the box. Hopefully, the days of having trouble compiling my projects because of not having the right assemblies are over.

top

Conclusion

This article has provided an overview of my .NET MIDI toolkit. My hope is that it will be a useful and powerful tool for writing MIDI applications. It has been a lot of fun to write. Each version has represented the very best of my skill and knowledge as a programmer. I welcome feedback and any bug reports you may have. Take care and thanks for your time.

top

History

  • 20th Feb, 2004
    • Article submitted.
  • 07th May, 2004
    • Second major version.
  • 27th Oct, 2004
    • Third major version.
  • 08th March, 2006
    • Fourth major version.
  • 14th March, 2006
    • Cleaned up the article a bit.
    • Consolidated the sources into one generic interface in the Multimedia namespace.
    • Consolidated all of the sinks into one generic delegate also in the Multimedia namespace.
    • Fixed a bug in the MIDI File Player demo as well as a bug in the Sequence class.
  • 14th April, 2007
    • Fifth major version.

top

License

This article, along with any associated source code and files, is licensed under The MIT License

About the Author

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

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
BugFound some bugs with OutputStreammemberMarkOBrien5 Apr '13 - 15:12 
Hi,
 
Great toolkit! I'm looking forward to using it in an algorithmic composition project of mine.
 
However I found some bugs with OutputStream, so you might want to fix the code?
 
OutputStream.Close:
if(!IsDisposed)
should be:
if(IsDisposed)
 
Unless this is fixed, closing the stream does nothing, (and the program will not usually exit until the stream is closed).
 
MidiHeaderBuilder.Build:
header.bytesRecorded = 0;
should be:
header.bytesRecorded = BufferLength;
Unless this is fixed, Windows thinks that there is no data to play and ignores the buffer. No MIDI events will play and the attempt to stream results in MOM_DONE immediately.
 
Also, I found it was necessary to add a Done event to OutputStream (following the pattern you used for NoOpOccurred) so that client code can know when to close the stream or send it more data or whatever.
 
Thanks for the great toolkit. I know you're not active with it any more, but I hope you can add in these simple fixes anyway so that nobody else will have to spend the time I did to figure out why it wasn't working.
QuestionUse MIDI ToolKit on Windows PhonememberFreddC201131 Mar '13 - 11:36 
Hi
 
Is possible use the MIDI ToolKit for Windows Phone ??
QuestionInvalid pulses per quarter note valuememberAmr Hesham2 Feb '13 - 14:29 
I always receive an "Invalid pulses per quarter note value" exception whenever trying to play a MIDI file generate by an application I wrote. I've set ticks per quarter note to 24, in the MIDI file header. Any idea why I receive this exception?
AnswerRe: Invalid pulses per quarter note valuememberMulleDK192 Apr '13 - 13:10 
Ya, I got this too.
 
I fixed it by simply opening up Clocks\PpqnClock.cs and changing PpqnMinValue from 24 to 8.
MulleDK13 (http://www.splintercell3.treesoft.dk)

GeneralRe: Invalid pulses per quarter note valuememberWeenley12 May '13 - 1:33 
I don't think it's a good idea to set the value to 8. In the MidiFileProperties replace the value with this:
value = (int)(Math.Round((double)value / PpqnClock.PpqnMinValue) * PpqnClock.PpqnMinValue);
and it should do.
 
The problem is that the value that you get has to be dividable by our PpqnMinValue so if we for example have 1024 it's not dividable by the value 24. 1024 / 24 = 42,66~.
So we divide our value with the PpqnMinValue, round it up or down and multiple it again with 24.
1024 / 24 = 42,66 -> Math.Round -> 43 * 24 = 1032. And there we have a valid value.
QuestionMIDI datamemberJeanineFr22 Jan '13 - 5:02 
I was wondering if it is possible to see MIDI data (like binary numbers) with your great tool. Can you please discribe this to me?
Question64 bit stuffmemberShof26 Dec '12 - 8:00 
I have been using your great toolkit for some time. I am still using V4 since it is working well for me and I have a lot of code based on it. Anyway, I have a customer who tried running on a 64 bit W7 machine and as I'm sure you're not surprised it had problems. I saw you post about the pointers and it doesn't look like there are that many in the code. Are there any other changes that you know of to make this go? I appreciate all your work and look forward to hearing from you.
AnswerRe: 64 bit stuffmemberShof26 Dec '12 - 13:58 
I have changed all of the calls as suggested but now other things don't work in particular I now get an invalid cast at..
 
private void HandleShortMessage(object state)
{
int message = (int)state;
 
So I don't know if you ever converted the lib to support 64 bit. In previous messages you said that you were working on it but that was the last I saw. Please give me a status update.
GeneralRe: 64 bit stuffmemberLeslie Sanford26 Dec '12 - 14:36 
Unfortunately, I haven't worked on the lib in several years. This is just a guess, but does .NET have an Int64 pointer? If so, maybe the Int32s need to be switched over. Better yet, is there a .NET pointer type that ports to the correct size regardless of platform?
 
Sorry I can't be of more help. I've been out of the .NET/C# world for a long time now.
GeneralRe: 64 bit stuffmemberShof26 Dec '12 - 15:00 
That is unfortunate. I take it from your response that you imagine V5 would have the same problems.
SuggestionPlease add NuGet packagememberShimmy Weitzhandler18 Dec '12 - 1:47 
NuGet rules today.
Shimmy

QuestionHow can i get the pressed keys?memberMember 95527144 Dec '12 - 10:11 
I want to get the pressed keys value when i press a key for example. I need to make a musical sheet using the returned notes.
GeneralMy vote of 5memberMember 95527144 Dec '12 - 4:13 
i was looking for this all over the internet, and it's a perfect kit, it gave me more than i needed.thank you
GeneralMy vote of 5memberGarfieldJiang18 Nov '12 - 16:37 
Very helpful!I'm develop a little application with midi in C#.
QuestionMidi out and vb.net 2005 [modified]memberMember 814518224 Oct '12 - 11:28 
Hi all, I know this is a stupid question, but it's a lot of time I don't write any lines of code.
I'm trying to use this library in a vb .net 2005 project, but probably I'm doing something wrong.
Here is a code snippet I've written til now:
'***************THIS PART OF CODE WORKS GOOD:
Public Class Form1
    Friend WithEvents midiIn As InputDevice
    Friend WithEvents midiout As OutputDevice
    Friend WithEvents sequence1 As Sequence
    Friend WithEvents sequencer1 As Sequencer
 
    Private Sub Button3_MouseDown(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Button3.MouseDown
        midiout = New OutputDevice(0)
        midiout.Send(New ChannelMessage(ChannelCommand.NoteOn, 0, 34, 127))
    End Sub
    Private Sub Button3_MouseUp(ByVal sender As Object, ByVal e As System.Windows.Forms.MouseEventArgs) Handles Button3.MouseUp
        midiout.Send(New ChannelMessage(ChannelCommand.NoteOff, 0, 34, 0))
        midiout.Close()
        midiout.Dispose()
    End Sub
'***************Maybe something wrong here below?
    Private Sub Button2_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles Button2.Click
        midiout = New OutputDevice(0)
        sequence1 = New Sequence
        sequencer1 = New Sequencer
        sequence1.LoadAsync(midifilename+path)
        sequencer1.Start()
    End Sub
Private Sub HandleLoadCompleted(ByVal sender As Object, ByVal e As System.ComponentModel.AsyncCompletedEventArgs) Handles sequence1.LoadCompleted
        	'code just to see at runtime if this sub is called, and sequence1 loads midifile.  working until here 
		MessageBox.Show("handleloadcompleted")
    End Sub
 
Here below is where I'm having troubles, no compiling errors but this subs are never called. and of consequence midi file dont plays. What I'm doing wrong?
 
Private Sub HandleChannelMessagePlayed(ByVal sender As Object, ByVal e As Sanford.Multimedia.Midi.ChannelMessageEventArgs) Handles sequencer1.ChannelMessagePlayed
    End Sub
 
    Private Sub HandleChased(ByVal sender As Object, ByVal e As Sanford.Multimedia.Midi.ChasedEventArgs) Handles sequencer1.Chased
    End Sub


modified 26 Oct '12 - 21:34.

Questionrecive wrong MIDI Data from Roland TD-9memberKillerregenwurm5 Sep '12 - 23:03 
Hey,
 
first I want to gratulate you for this great lib. But unfortunately I have some problems with my project. I actual work on a Programm which records live Drum Imput and displays it as a Drumscore.
 
The maintime it works great, but I can see in the log, that there are a lot of wrong events like, noteOff before noteOn or only noteOff or suddenly a wrong chanel or it seems sometimes that data1 and data2 are switched.
 
Now I want to know whether it is possible, that this wrong data comes from your lib or (what I supposed to) the Midi interface works not correctly. I get the same input with your demo code, so I think it is not my implementation of your lib. But maybe the in incoming informations are coming to quick?
 
greetings
Sven
SuggestionDetermining the length of a MIDI file in secondsmemberTomiSoft (std66)24 Aug '12 - 12:31 
Hi!
 
Some people have asked for help to get the MIDI file's length in seconds. Here is the solution.
 
You will need to find all the TempoChange messages in the tracks. In my example I will use only the first tempo change. First of all, modify the Load method in the sequence.cs file.
 
//sequence.cs

private bool FirstTempoFound = false;
public int FirstTempo = 0;
 
public void Load(string fileName)
{                   
  FileStream stream = new FileStream(fileName, FileMode.Open,
    FileAccess.Read, FileShare.Read);
 
  using(stream)
  {
    //...

    newProperties.Read(stream);
 
    for(int i = 0; i < newProperties.TrackCount; i++)
    {
      reader.Read(stream);
      newTracks.Add(reader.Track);
 
      for (int Counter = 0; Counter < reader.Track.Count; Counter++) {
        if (this.FirstTempoFound) {
          break;
        }
 
        if (reader.Track.GetMidiEvent(Counter).MidiMessage.MessageType == MessageType.Meta) {
          MetaMessage Msg = (MetaMessage)reader.Track.GetMidiEvent(Counter).MidiMessage;
          if (Msg.MetaType == MetaType.Tempo) {
            TempoChangeBuilder Builder = new TempoChangeBuilder(Msg);
            this.FirstTempo = Builder.Tempo;
 
            this.FirstTempoFound = true;
 
            break;
          }
        }
      }
    }
 
    //...
}
 
If you have the tempo, you can calculate the length:
 
int LengthInSeconds = (int)((this.Sequence.GetLength() / this.Sequence.Division) * this.Sequence.FirstTempo / 1000000f);
 
The playback position also can be calculated using the same way.
QuestionDetect MIDI chord and the current beat in a barmemberMember 857547613 Jul '12 - 12:42 
Hi!
 
This toolkit is awesome and really easy to use. I'm developing a software that plays many kinds of audio files, including MIDI. I'd like to implement some new features.
 
One of them is displaying the currently played chord name. I have a library to determine the chord name from notes. Is there any way to detect those channels which contains the chords or retreive the current chord's notes?
 
I also want to display the current beat position in a bar (e.g. the MIDI's time signature is 6/8, and the position is 4/8 in the current bar). How can I determine the position in the current bar?
 
Thank you in advance.
AnswerRe: Detect MIDI chord and the current beat in a barmemberMember 83788303 Oct '12 - 12:07 
In MIDI format, there is no measure bar indicator or chord.
GeneralRe: Detect MIDI chord and the current beat in a bar [modified]memberTomiSoft (std66)4 Oct '12 - 13:30 
Well, I managed to calculate the measures. I use this code (using the the sequencer's clock's Tick event):
 
int TotalMeasures = (int)(((float)this.Sequencer.Position / this.Sequence.Division) * ((float)this.TimeSign_Denom) / 4.0f);
int BeatPosInBar = (TotalMeasures % this.TimeSign_Num) + 1;
 
TimeSign_Num and TimeSign_Denom variables represents the last time signature's numerator and denominator.

modified 18 Oct '12 - 13:42.

SuggestionThis work is great! More articles? Nuget package? SVN or GIT?memberMetal_WarriorOfICE29 Jun '12 - 4:23 
This is a great piece of code!! Big Grin | :-D
I suggest publish as Nuget package in order to do massive deployment.
You can move to a SVN or GIT repo to mantain easily
GeneralRe: This work is great! More articles? Nuget package? SVN or GIT?memberShimmy Weitzhandler18 Dec '12 - 1:47 
Yeah!
I also think so!
Shimmy

QuestionIs there a way to tell if a device has been lostmembercatchit200015 Mar '12 - 14:32 
Is there a way to tell if a device has been lost? Crashed or disconnected, USB connection lost??? Specifically I looking at an Input Device.
QuestionAWESOME toolkit. multiple input devices - how to handlemembercatchit20006 Feb '12 - 10:50 
First, I'd like to say this is toolkit is awesome and has been a great help to me in writing test code for midi devices.
I'm trying to record messages from multiple input devices and I want to put a filter in place that needs to know which device the message came from. Is there a way to do that. There was a question about multiple output device in here somewhere (mad-matt) but I wasn't sure if that also applied to multiple inputs. I didn't really understand the explanation applying to inputs.
thanks a lot!
AnswerRe: AWESOME toolkit. multiple input devices - how to handlememberReinhold Behringer15 Feb '12 - 0:55 
Hi,
as far as I know, the event handlers (e.g. ChannelMessageReceived etc.) are bound only to one InputDevice at a time. So if you want to use several inputs simultaneously, you would need to write several independent handlers, one for each input. If you would just use the same handler, you would not be able to differentiate where the MIDI in data would come from; unless you write the handler in such a way that the MIDI in device would somehow show up as another input parameter. I believe this is possible, but would require more in-depth programming knowledge about writing event handlers.
Maybe it is possible to create an array of event handlers, and then link each MidiInput to one of these handlers??
 
Another idea: I noticed that the event handler for ChannelMessageReceived does have a "sender" input. This should be able to identify where the message came from, so you could in fact use the same event handler for each of the MIDI input devices.
GeneralRe: AWESOME toolkit. multiple input devices - how to handlemembercatchit200027 Feb '12 - 13:40 
Thank you. I was able to do this by creating a deviceIndex in InputDevice.Properties and passing it down through ChannelMassageEventArgs
Questionsmall problem with MidiInDevice: does not reset of close...memberReinhold Behringer3 Feb '12 - 6:39 
Hi,
this is an EXCELLENT software package, and it is very intuitive and clear to use, thanks to the very good description.
 
I am writing a little experimental program in C#, with VS2010, and ran into an issue with an external USB MIDI keyboard: it shows up properly in the MidiIn devices, and I can record from it. However, after I do StopRecording() and want to either Reset() or Close(), the software hangs. With a debugger I have identified this part in the function
public override void Reset()
in the file
 InputDevice.PublicMethods.cs
 
...
while(bufferCount > 0)
         {
             Monitor.Wait(lockObject);
         }
 

bufferCount is 4; seems to come from 4 SysEx buffers that have been allocated earlier. But I have no idea what lockObject is, nor why it waits there in the Monitor.Wait() function forever. Does not leave there.
This happens only after StartRecording() has been used.
 
Any idea why this is???? And what I could do to solve this?
 
Cheers,
Reinhold
Questionhow can record ?membershadiabusamra31 Dec '11 - 5:30 
how can record midi that play on keyboard
Questiona small bugmemberenochenoch2k4 Dec '11 - 22:14 
this : outDevice = new OutputDevice(outDeviceID)
 
when outDeviceID is out of range,the program not exit ok!
QuestionQuality work!memberMember 84518811 Dec '11 - 15:35 
I just wanted to stop by and say what quality work you've done. Your code style shows that you're incredibly attentive to detail and that you're intolerant of bugs!
 
I'm working on a quick project to generate a click track from a MIDI file and I was able to put together a single LINQ query over your object model to find all the TimeSignature and Tempo meta messages within 20 minutes of downloading your sample project. Kudos to you! This is a serious time saver. Smile | :)
QuestionmidiStreamStop or midiStreamReset crash occasionally...memberMember 331069522 Nov '11 - 5:45 
I was using the old c++ Mabry driver for my products (rewritten) however...I have given up on the 'ol c++ driver and have rewritten Les' toolkit to look like the Mabry driver from an interface point of view. It works well for the most part. I use OutputDevice when the transport is not engaged and the OutputStream when engaged. I dump real time buffers into the stream for fader changes while playing/recording and still use the sequencer, without OutputDevice (cannot have both) for realtime fader and LED feedback. I am seeing an intermittent issue sometimes when I stop the stream, the call to midistreamstop (or midistreamreset) hangs...trying to figure out how to recover, the handle looks good. I am totally impressed with Les' coding!
AnswerRe: midiStreamStop or midiStreamReset crash occasionally...memberMember 331069522 Nov '11 - 9:53 
Oh, I got past the crash problem...just has to do a midistreampause before the midistreamstop...
QuestionHow to change Tempomemberbajerek9 Sep '11 - 0:29 
Hi everyone!
MIDI Toolkit wrote by Leslie Sanford is very excellent, but I have a big problem, because I would like to be able change tempo. I know that dynamically change tempo is very difficult to write in C#, but i would like change tempo at least when I turn on program, but earlier than playing music. Tempo is set in class "PpgnClock.cs" as 'DefaultTempo'. how do I make, that DefaultTempo set for example by the control.
Thanks for any answers and please help me.
AnswerRe: How to change Tempo [modified]memberEmre Gönültaş28 Dec '11 - 4:04 
I am not an expert at this but I added this piece of code under sequencer class:
public void changeTempo(int tempo)
{
    TempoChangeBuilder tc = new TempoChangeBuilder();
    tc.Tempo = tempo;
    tc.Build();
    clock.Process(tc.Result);
}
 
Via that I am able to change the tempo by calling sequencer1.changeTempo(500000) even at runtime.
 

You should also consider commenting out the
else
{
   clock.Process(e.Message);
}
 
part in Sequencer constructor since at runtime the tempo can change (if there is such a content in the MIDI file) and it can be incompatible with your tempo.

modified 28 Dec '11 - 10:16.

QuestionTrackNamememberhelbekah16 Aug '11 - 2:10 
Hi I want to iterate through a MIDI file and spit out data depending on what the track's names are.
I cant work out how to display the track's names when sorting through the MIDI events. Is it possible to do it with this c# MIDI toolkit?
AnswerRe: TrackNamememberEmre Gönültaş28 Dec '11 - 3:53 
The toolkit is easy to extend to store tracknames. Trackname is a meta message, so it is read under TrackReader's ParseMetaMessage() method. There you can add:
 
else if (type == MetaType.TrackName)
{
    byte[] data = new byte[ReadVariableLengthValue()];
    Array.Copy(trackData, trackIndex, data, 0, data.Length);
    newTrack.Insert(ticks, new MetaMessage(type, data));
 

    track.Name = System.Text.ASCIIEncoding.ASCII.GetString(data);
    trackIndex += data.Length;
}
 
and ta-daa
Questionfile loading performance ( bug? )memberPeteRainbow22 Jul '11 - 20:15 
hiya,
 
great software, but Smile | :)
 
when running on my slow laptop i noticed a big perf issue when loading files.
 
i have debugged this and pinned down to a 'logic' flaw..
 
when you are adding each message to the track there is an AssertValid call
 
when looking at the AssertValid it then scans through the whole track!!
 
this means you have an exponential increase in loading
 
as for each message added we then scan all the previous messages
 
when i commented out this AssertValid it loaded the file < 1 sec
 
with this line took 1 minute!
 
obviously this is only in debug build, but seems heavy handed.
 
maybe change to just have one assert at the end of the load?
QuestionMicro Tonesmember77Chris7728 Jun '11 - 9:32 
IS it possible to play micro tones like 0.5 which would be half C sharp?
QuestionInputDevice: midiInPrepareHeader and MMSYSERR_INVALPARAM errormemberonereason17 Jun '11 - 13:52 
Hi,
 
Did anybody get that error when trying to use InputDevice or knows what's going on? I'm trying to force MIDIToolkit v5 to listen to my MIDI keyboard, but all I have achieved is just to list input and output MIDI devices. I use the following code:
 
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
    InputDevice inDevice = null;
    try
    {
        if (InputDevice.DeviceCount > 0)
        {
            inDevice = new InputDevice(0);
 
            inDevice.ChannelMessageReceived += new EventHandler<ChannelMessageEventArgs>(ChannelMessageHandler);
 
            inDevice.StartRecording();
 
            Thread.Sleep(10000);
 
            inDevice.StopRecording();
        }
        else
        {
            Console.WriteLine("No input devices.");
        }
    }
    catch (Exception ex)
    {
        Console.WriteLine(ex.Message);
    }
    finally
    {
        if (inDevice != null)
        {
            inDevice.Dispose();
        }
 
        Console.WriteLine("Press any key...");
        Console.ReadKey(true);
    }
}
 
// Handles channel messages.
public static void ChannelMessageHandler(object sender, ChannelMessageEventArgs e)
{
    Console.WriteLine(e.Message.Command);
}
 
Shortly speaking, the line inDevice.StartRecording(); fails to create MIDIInHeader. Debugger took me to the following line:
result = midiInPrepareHeader(Handle, headerPtr, SizeOfMidiHeader);
(file InputDevice.Messaging.cs, method AddSysExBuffer()). The result has always value of 11, what is MMSYSERR_INVALPARAM actually. According to the MSDN, there's a problem with the headerPtr argument ("The specified address is invalid"), but I'm out of ideas what's wrong with it. I use WinXP and VS2008 working on this.
 
Any help will be much appreciated..
AnswerRe: InputDevice: midiInPrepareHeader and MMSYSERR_INVALPARAM errormemberDaichy7 Jul '11 - 3:52 
Hi,
 
I use Win7 and VS2008 working on this.
 
Though I don't know a mean which resolve your problem, I'm success to listen channel messages by use of your code which indicated above.
 
thx
AnswerRe: InputDevice: midiInPrepareHeader and MMSYSERR_INVALPARAM errormemberdoctorwho60113 Oct '11 - 15:27 
I'm playing with the library and I have a combo box to select my midi input and one for my output. So I did the following. Of course I had to create the variables inDevice, InDeviceID, and the Combobox, but to test it out you can just plug in 0 or 1 for the ID.
I've only been playing with the libraries since this morning. There might be a better way out there. There are a lot of good posts that I've found.
 
It works fine. Very low latency.
 
inDevice.Dispose();
            inDeviceID = ((ComboBox)sender).SelectedIndex;
 
             inDevice = new InputDevice(inDeviceID);
            ChannelStopper stopper = new ChannelStopper();
 
            inDevice.ChannelMessageReceived += delegate(object senderx, ChannelMessageEventArgs ex)
            {
                // send the input to the output device.
                outDevice.Send(ex.Message);
                stopper.Process(ex.Message);
            };
 
            inDevice.StartRecording();

GeneralnugetmemberMember 205900425 May '11 - 11:13 
have you given any thought to uploading this project to nuget? it would be a very valuable contribution IMHO
General"Output devices" issuememberbimbambumbum29 Apr '11 - 14:04 
Hi
 
Just downloaded your project and got the program up running. When I press "Output Devices" nothing happens - any idea? I can't make my external MIDI-keyboard talk to your program.
 
I have tested my MIDI connectivity using another sequencer program and everything works well there.
 
I'm using Windows 7 with a UX16 USB MIDI-interface.
 
Would be great to get this working.
 
Cheers

GeneralRe: "Output devices" issue [modified]memberdoctorwho60112 Oct '11 - 9:26 
I'm retracting my message I posted a minute ago as I looked closer at the code. While this works it isn't probably the best. I could not find any code in the demo that reset the MIDI device. Also, the dropdown listbox for the devices wasn't working. This one fix should fix both problems.
 
Replace the empty function '
 outputDeviceToolStripMenuItem_Click
'with this one:
 

private void outputDeviceToolStripMenuItem_Click(object sender, EventArgs e)
       {
           outDialog.SetDesktopLocation(this.DesktopLocation.X, this.DesktopLocation.Y);
           if (outDialog.ShowDialog() == DialogResult.OK)
           {
               try
               {
                   outDevice.Dispose();
                   outDeviceID = outDialog.OutputDeviceID;
                   outDevice = new OutputDevice(outDeviceID);
 
                   sequence1.LoadProgressChanged += HandleLoadProgressChanged;
                   sequence1.LoadCompleted += HandleLoadCompleted;
               }
               catch (Exception ex)
               {
                   MessageBox.Show(ex.Message, "Error!",
                       MessageBoxButtons.OK, MessageBoxIcon.Stop);
 
                   Close();
               }
           }
       }


modified 12 Oct '11 - 16:24.

GeneralRe: "Output devices" issuememberdoctorwho60112 Oct '11 - 10:26 
BTW, I just copied the code from where the original device was created. The 'sequence1' code is most probably not necessary but I'm too lazy and tired to try removing it. Smile | :)
GeneralCould the Microsoft GS Wavetable Synth as an InputDevice?membercatycatylee21 Apr '11 - 8:15 
Thanks for the author, your toolkit,it's really useful!
But now i have some problem of recording data!
 
My project is using the OutputDevice to send data and generate the sound through PC,
but I want to record the generate message as a midi file
what should I do?
 
And I try to use in InputDevice to Recording
but it always pomp out the "inputDevice ID is out of range of local System"...
am I wrong? Could the Microsoft GS Wavetable Synth as an InputDevice?
 
Please answer my question... Thank you very very muchSmile | :)
GeneralKudos!!!memberRags151210 Apr '11 - 8:10 
Very well written and structured article. Not only provides wrapper to MIDI but explains handling of MIDI messages in code.
Praveen Raghuvanshi
Software Developer

QuestionLatency theme again.memberSergey Markin27 Mar '11 - 9:27 
Many thanks Leslie for the toolkit. It is really great and I have the question about the latency.
I have reduced MidiWatcher to the simplest state and run in Release mode without DEBUG predefined constant.
But using the real midi-keyboard gets the latency ~50ms. Interesting material - http://forum.cockos.com/archive/index.php/t-34533.html[^]
 
"When recording and playing MIDI you care about three kinds of timing: jitter, audio sync, and latency.
 
Jitter is how consistent or inconsistent the timing between notes is. If you have a quantized MIDI beat, you want it to actually play quantized instead of hits being randomly early or late.
 
There are systemic limitations on how low jitter can be with any hardware. A couple of good articles about MIDI timing and Windows:
http://www.soundonsound.com/sos/dec07/articles/cubasetech_1207.htm (e-sub)
http://www.tim-carter.com/music-production/midi-latency.php
 
The summary is that there is a minimum MIDI jitter on Windows in the 1 ms range, with the additional problem that each device must make a decision about what kind of timestamps to use, and mismatched timestamp assumptions can cause terrible jitter in the 50 ms range."
 
Can C# Midi Toolkit handle this in any way?
 
using System;
using System.Threading;
using System.Windows.Forms;
using Sanford.Multimedia;
using Sanford.Multimedia.Midi;
 
namespace MidiWatcher
{
    public partial class Form1 : Form
    {
        private InputDevice inDevice = null;
        private OutputDevice outDevice = null;
 
        private SynchronizationContext context;
        
        public Form1()
        {
            InitializeComponent();
        }
 
        protected override void OnLoad(EventArgs e)
        {
            context = SynchronizationContext.Current;
 
            inDevice = new InputDevice(0);
            outDevice = new OutputDevice(0); // Miscrosoft GS Wavetable Synth
            inDevice.ChannelMessageReceived += HandleChannelMessageReceived;
            base.OnLoad(e);
        }            
 
        protected override void OnClosed(EventArgs e)
        {
            outDevice.Close();
            inDevice.Close();
            base.OnClosed(e);
        }
 
        private void startButton_Click(object sender, EventArgs e)
        {
            inDevice.StartRecording();
        }
 
        private void stopButton_Click(object sender, EventArgs e)
        {
            inDevice.StopRecording();
            inDevice.Reset();
        }
 
        private void HandleChannelMessageReceived(object sender, ChannelMessageEventArgs e)
        {
            context.Post(delegate(object dummy)
            {
                outDevice.Send(e.Message);
            }, null);
        }
    }
}

GeneralArgumentOutOfRangeException MIDI header buffer length out of range.memberRogueTrooper18 Feb '11 - 14:42 
This exception was thrown when making a call to Sanford.Multimedia.Midi.InputDevice.StartRecording(). This used to work flawlessly using Windows XP and VS2005/2008, but now it doesn't in Win7 64/VS2010. Apparently the buffer size seems to be set to 0 which explains the exception. I have tried manually setting the buffer length to 256(which is the size of a sysex patch), but then I get a CallbackOnCollectedDelegate exception.
GeneralRe: ArgumentOutOfRangeException MIDI header buffer length out of range.memberMarek Ledvina4 Apr '11 - 18:25 
Rogue, have you found a solution for this problem? Please let me know. Thank you.

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

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