4

So I'm trying to play single notes in C# using midiOutShortMsg(). The problem is that no sound is played. The one way I've figured out to play the note is by putting the midiOutShortMsg() in a for loop from i=0 to 10000. But I don't believe that's how the API is supposed to work.

Later in the project I want to implement the MIDI into a Kinect project, and having the for loop delays the real-time feedback of the Kinect. So the for loop method is a no-go.

Below is the code I am using to play notes, if you comment out the for loop then no sound will play. Any help would be appreciated.

using System;
using System.Runtime.InteropServices;
using System.Text;

namespace MIDITest
{

    [StructLayout(LayoutKind.Sequential)]
    public struct MidiOutCaps
    {
        public UInt16 wMid;
        public UInt16 wPid;
        public UInt32 vDriverVersion;

        [MarshalAs(UnmanagedType.ByValTStr,
           SizeConst = 32)]
        public String szPname;

        public UInt16 wTechnology;
        public UInt16 wVoices;
        public UInt16 wNotes;
        public UInt16 wChannelMask;
        public UInt32 dwSupport;
    }

class Program
    {
        // MCI INterface
        [DllImport("winmm.dll")]
        private static extern long mciSendString(string command,
           StringBuilder returnValue, int returnLength,
           IntPtr winHandle);

        // Midi API
        [DllImport("winmm.dll")]
        private static extern int midiOutGetNumDevs();

        [DllImport("winmm.dll")]
        private static extern int midiOutGetDevCaps(Int32 uDeviceID,
           ref MidiOutCaps lpMidiOutCaps, UInt32 cbMidiOutCaps);

        [DllImport("winmm.dll")]
        private static extern int midiOutOpen(ref int handle,
           int deviceID, MidiCallBack proc, int instance, int flags);

        [DllImport("winmm.dll")]
        private static extern int midiOutShortMsg(int handle,
           int message);

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

        private delegate void MidiCallBack(int handle, int msg,
           int instance, int param1, int param2);

        static void Main()
        {
            int handle = 0;

            var numDevs = midiOutGetNumDevs();
            Console.WriteLine("You have {0} midi output devices", numDevs);
            MidiOutCaps myCaps = new MidiOutCaps();
            var res = midiOutGetDevCaps(0, ref myCaps,
                   (UInt32)Marshal.SizeOf(myCaps));

            res = midiOutOpen(ref handle, 0, null, 0, 0);

            byte[] data = new byte[4];

            data[0] = 0x90;
            data[1] = 50;
            data[2] = 111;
            uint msg = BitConverter.ToUInt32(data, 0);

            for (int i = 0; i < 10000; i++)
            {
                // both hard coding the message and creating it with byte doesn't work
                //res = midiOutShortMsg(handle, 0x007F4A90);
                res = midiOutShortMsg(handle, (int)msg);
            }
            res = midiOutClose(handle);
            Console.ReadLine();
        }
    }
}
Bassie
  • 9,529
  • 8
  • 68
  • 159
Shinsuke
  • 51
  • 3
  • 2
    I think that this approach is misguided. Use an existing MIDI library to do this. I have a lot of respect for [naudio](https://github.com/naudio/NAudio). It already contains a managed wrapper for MIDI IO that should be well-tested. Why don't you use it instead of wasting a huge amount of time on writing a midi wrapper, worrying about byte ordering and the like? It will save you a ton of time and has universal app support out of the box. You can [nuget it into your project](https://www.nuget.org/packages/NAudio/) easily. – spender Nov 03 '16 at 01:47
  • Hi spender, thank you so much for your response! I've been working with naudio all day today, but I found that playing single notes also require the use of System.Threading.Thread.Sleep(). And I am getting the same issue where playing a single note will halt the whole program and no sound is played. Would you happen to know any workarounds? – Shinsuke Nov 07 '16 at 01:37
  • It seems Your sound is starts playing... for a little time, but you Close the driver. If You do not want to wait, then use timing: Start a sound, go elsewhere, and when the time has come, come back, and stop that sound, and/or start another. If your program is over, close the device. – CLS Jun 09 '20 at 14:11

1 Answers1

1

This is because midiOutShortMsg does not stop the execution of the code, meaning that midiOutClose gets called before the note has had time to play.

One way to overcome this would be to add a Sleep:

res = midiOutShortMsg(handle, (int)msg);

if (res == 0) // Check success code
{
    System.Threading.Thread.Sleep(length);
}

res = midiOutClose(handle);

Where length is the amount of time in ms it takes for the note to finish playing.

However this is almost certainly not reommended.

Bassie
  • 9,529
  • 8
  • 68
  • 159
  • 1
    Doing anything with normal windows timers with MIDI will give timing so sloppy that it would be recognizable to even the most musically illiterate. – spender Nov 03 '16 at 01:59
  • Thanks for your response Bassie! Unfortunately with System.Threading.Thread.Sleep(), it halts the whole program since the Kinect is single threaded by default. I am looking into multithreading, but the kinect updates at 30 frames per second, I don't want to accidentally create crazy amount of threads and was just wondering if you knew other workarounds! – Shinsuke Nov 07 '16 at 01:39
  • 1
    @Shinsuke No problem - you could check if the application is outputting any sound, and don't call `midiOutClose` until the sound is no longer playing. – Bassie Nov 10 '16 at 11:08