20

I'm trying to rebuild an old metronome application that was originally written using MFC in C++ to be written in .NET using C#. One of the issues I'm running into is playing the midi files that are used to represent the metronome "clicks".

I've found a few articles online about playing MIDI in .NET, but most of them seem to rely on custom libraries that someone has cobbled together and made available. I'm not averse to using these, but I'd rather understand for myself how this is being done, since it seems like it should be a mostly trivial exercise.

So, am I missing something? Or is it just difficult to use MIDI inside of a .NET application?

soumya
  • 3,801
  • 9
  • 35
  • 69
jerhinesmith
  • 15,214
  • 17
  • 62
  • 89

11 Answers11

12

I'm working on a C# MIDI application at the moment, and the others are right - you need to use p/invoke for this. I'm rolling my own as that seemed more appropriate for the application (I only need a small subset of MIDI functionality), but for your purposes the C# MIDI Toolkit might be a better fit. It is at least the best .NET MIDI library I found, and I searched extensively before starting the project.

Kjetil Limkjær
  • 1,550
  • 12
  • 22
  • 2
    Leslie's MIDI Toolkit is definitely the most comprehensive C# solution to playing, recording MIDI. I have used it for a very complex project and it worked well. – andynormancx Dec 19 '09 at 09:36
10

I think you'll need to p/invoke out to the windows api to be able to play midi files from .net.

This codeproject article does a good job on explaining how to do this: vb.net article to play midi files

To rewrite this is c# you'd need the following import statement for mciSendString:

[DllImport("winmm.dll")] 
static extern Int32 mciSendString(String command, StringBuilder buffer, 
                                  Int32 bufferSize, IntPtr hwndCallback);

Hope this helps - good luck!

John Sibly
  • 22,782
  • 7
  • 63
  • 80
  • This answer is acknowledged, but incomplete and (very) outdated. When you want to create a metronome, proper way is to let the sound card decide the timing, not Windows. Just beeping out mciSendString for each tick won't do, the string should contain all ticks and loop somehow. – Goodies Dec 30 '20 at 00:01
4

midi-dot-net got me up and running in minutes - lightweight and right-sized for my home project. It's also available on GitHub. (Not to be confused with the previously mentioned MIDI.NET, which also looks promising, I just never got around to it.)

Of course NAudio (also mentioned above) has tons of capability, but like the original poster I just wanted to play some notes and quickly read and understand the source code.

Paul Williams
  • 3,099
  • 38
  • 34
4

I think it's much better to use some library that which has advanced features for MIDI data playback instead of implementing it by your own. For example, with DryWetMIDI (I'm the author of it) to play MIDI file via default synthesizer (Microsoft GS Wavetable Synth):

using Melanchall.DryWetMidi.Devices;
using Melanchall.DryWetMidi.Core;

// ...

var midiFile = MidiFile.Read("Greatest song ever.mid");

using (var outputDevice = OutputDevice.GetByName("Microsoft GS Wavetable Synth"))
{
    midiFile.Play(outputDevice);
}

Play will block the calling thread until entire file played. To control playback of a MIDI file, obtain Playback object and use its Start/Stop methods (more details in the Playback article of the library docs):

var playback = midiFile.GetPlayback(outputDevice);

// You can even loop playback and speed it up
playback.Loop = true;
playback.Speed = 2.0;

playback.Start();

// ...

playback.Stop();

// ...

playback.Dispose();
outputDevice.Dispose();
Maxim
  • 1,995
  • 1
  • 19
  • 24
  • Thanks for the hard work, Maxim.. Used it in VS2017, now downloaded it via NuGet using VS2019. I really like to play with your library in the holiday times ! To add some info here: this lib does not only allow playback, it can create midi files, emit properly timed midi commands - like metronomes require - and it has a very convenient *managed* internal data structure to analyze Midi files ! Also very convenient for game developers.. playback.GetDuration() and the ability to Stop() and Start() the playback again. It can neatly fade out, finishing the current Note properly, no cracks on stop. – Goodies Dec 29 '20 at 23:51
1

For extensive MIDI and Wave manipulation in .NET, I think hands down NAudio is the solution (Also available via NuGet).

Shimmy Weitzhandler
  • 101,809
  • 122
  • 424
  • 632
1

A recent addition is MIDI.NET that supports Midi Ports, Midi Files and SysEx.

obiwanjacobi
  • 2,413
  • 17
  • 27
1

Sorry this question is a little old now, but the following worked for me (somewhat copied from Win32 - Midi looping with MCISendString):

[DllImport("winmm.dll")]
static extern Int32 mciSendString(String command, StringBuilder buffer, Int32 bufferSize, IntPtr hwndCallback);

public static void playMidi(String fileName, String alias)
{
  mciSendString("open " + fileName + " type sequencer alias " + alias, new StringBuilder(), 0, new IntPtr());
  mciSendString("play " + alias, new StringBuilder(), 0, new IntPtr());
}

public static void stopMidi(String alias)
{
  mciSendString("stop " + alias, null, 0, new IntPtr());
  mciSendString("close " + alias, null, 0, new IntPtr());
}

A full listing of command strings is given here. The cool part about this is you can just use different things besides sequencer to play different things, say waveaudio for playing .wav files. I can't figure out how to get it to play .mp3 though.

Also, note that the stop and close command must be sent on the same thread that the open and play commands were sent on, otherwise they will have no effect and the file will remain open. For example:

[DllImport("winmm.dll")]
static extern Int32 mciSendString(String command, StringBuilder buffer,
                                    Int32 bufferSize, IntPtr hwndCallback);

public static Dictionary<String, bool> playingMidi = new Dictionary<String, bool>();

public static void PlayMidi(String fileName, String alias)
{
    if (playingMidi.ContainsKey(alias))
        throw new Exception("Midi with alias '" + alias + "' is already playing");

    playingMidi.Add(alias, false);

    Thread stoppingThread = new Thread(() => { StartAndStopMidiWithDelay(fileName, alias); });
    stoppingThread.Start();
}

public static void StopMidiFromOtherThread(String alias)
{
    if (!playingMidi.ContainsKey(alias))
        return;

    playingMidi[alias] = true;
}

public static bool isPlaying(String alias)
{
    return playingMidi.ContainsKey(alias);
}

private static void StartAndStopMidiWithDelay(String fileName, String alias)
{
    mciSendString("open " + fileName + " type sequencer alias " + alias, null, 0, new IntPtr());
    mciSendString("play " + alias, null, 0, new IntPtr());

    StringBuilder result = new StringBuilder(100);
    mciSendString("set " + alias + " time format milliseconds", null, 0, new IntPtr());
    mciSendString("status " + alias + " length", result, 100, new IntPtr());

    int midiLengthInMilliseconds;
    Int32.TryParse(result.ToString(), out midiLengthInMilliseconds);

    Stopwatch timer = new Stopwatch();
    timer.Start();

    while(timer.ElapsedMilliseconds < midiLengthInMilliseconds && !playingMidi[alias])
    {

    }

    timer.Stop();

    StopMidi(alias);
}

private static void StopMidi(String alias)
{
    if (!playingMidi.ContainsKey(alias))
        throw new Exception("Midi with alias '" + alias + "' is already stopped");

    // Execute calls to close and stop the player, on the same thread as the play and open calls
    mciSendString("stop " + alias, null, 0, new IntPtr());
    mciSendString("close " + alias, null, 0, new IntPtr());

    playingMidi.Remove(alias);
}
Community
  • 1
  • 1
Phylliida
  • 4,217
  • 3
  • 22
  • 34
  • 1
    use mciSendString("open " + fileName + " alias " + alias, new StringBuilder(), 0, new IntPtr()); instead of mciSendString("open " + fileName + " type sequencer alias " + alias, new StringBuilder(), 0, new IntPtr()); can play .mp3 files (and a bunch more like .wav, .wma, etc.) – Silent Sojourner Jun 20 '17 at 16:13
  • Jan 2021 .NET 5 and this still works. Nice work. – Goodies Jan 30 '22 at 14:08
1

A new player emerges:

https://github.com/atsushieno/managed-midi

https://www.nuget.org/packages/managed-midi/

Not much in the way of documentation, but one focus of this library is cross platform support.

Ronnie Overby
  • 45,287
  • 73
  • 267
  • 346
1

You can use the media player:

using WMPLib;
//...
WindowsMediaPlayer wmp = new WindowsMediaPlayer();
wmp.URL = Path.Combine(Application.StartupPath ,"Resources/mymidi1.mid");
wmp.controls.play();
Carra
  • 17,808
  • 7
  • 62
  • 75
1

I can't claim to know much about it, but I don't think it's that straightforward - Carl Franklin of DotNetRocks fame has done a fair bit with it - have you seen his DNRTV?

Whisk
  • 3,287
  • 2
  • 30
  • 30
-2

System.Media.SoundPlayer is a good, simple way of playing WAV files. WAV files have some advantages over MIDI, one of them being that you can control precisely what each instrument sounds like (rather than relying on the computer's built-in synthesizer).

MusiGenesis
  • 74,184
  • 40
  • 190
  • 334