Asked by:
Using winmm.dll to generate MIDI audio

-
The topic of working with MIDI files from VB.Net comes up from time to time and generally, if you just want to play a MIDI file, the easiest solution is to use an instance of MediaPlayer which can play MIDI files.
But if you want to actually control the MIDI device and play music note-by-note, then things get a little more involved. The winmm.dll offers an API over MIDI devices, but requires unmanaged code to utilize. There are a number of wrappers available but they tend to be cumbersome and/or bloated for simple audio playback. The winmm.dll provides full functionality for both MIDI Input and MIDI Output devices, but much of this functionality is unneeded when the only goal is to generate some MIDI music from code.
To resolve this issue and provide the basic foundation for MIDI interfacing with VB.Net, the following code will introduce the SimpleMidiEngine API for generating MIDI music via code, or from simple strings of note information.
A CodeProject Article was used as reference for the winmm implementation, along with the Microsoft reference documentation. Instrument names were taken from this site, while general MIDI message information was found here.
The first class in the project is the NativeMethods class which wraps the winmm API calls and provides the required managed types:
Option Strict On Imports System.Runtime.InteropServices 'translated in part from: https://www.codeproject.com/articles/6228/c-midi-toolkit Public NotInheritable Class NativeMethods Public Delegate Sub MidiOutProc(handle As IntPtr, msg As Integer, instance As Integer, param1 As Integer, param2 As Integer) Protected Declare Auto Function midiOutOpen Lib "winmm.dll" (ByRef handle As IntPtr, deviceId As Integer, proc As MidiOutProc, instance As Integer, flags As Integer) As Integer Protected Declare Auto Function midiOutClose Lib "winmm.dll" (handle As IntPtr) As Integer Protected Declare Auto Function midiOutReset Lib "winmm.dll" (handle As IntPtr) As Integer Protected Declare Auto Function midiOutShortMsg Lib "winmm.dll" (handle As IntPtr, message As Integer) As Integer Protected Declare Auto Function midiOutGetDevCaps Lib "winmm.dll" (handle As IntPtr, ByRef caps As MidiOutCaps, sizeOfmidiOutCaps As Integer) As Integer Protected Declare Auto Function midiOutGetNumDevs Lib "winmm.dll" () As Integer Protected Const MMSYSERR_NOERROR As Integer = 0 Public Const CALLBACK_FUNCTION As Integer = &H30000 Public Shared ReadOnly Property MidiOutCapsSize As Integer = Marshal.SizeOf(Of MidiOutCaps) Public Shared Sub OpenPlaybackDevice(ByRef handle As IntPtr, deviceId As Integer, proc As MidiOutProc, instance As Integer, flags As Integer) If Not midiOutOpen(handle, deviceId, proc, instance, flags) = MMSYSERR_NOERROR Then Throw New Exception("Open failed") End Sub Public Shared Sub ClosePlaybackDevice(handle As IntPtr) If Not midiOutClose(handle) = MMSYSERR_NOERROR Then Throw New Exception("Close failed") End Sub Public Shared Sub ResetPlaybackDevice(handle As IntPtr) If Not midiOutReset(handle) = MMSYSERR_NOERROR Then Throw New Exception("Reset failed") End Sub Public Shared Sub SendPlaybackDeviceMessage(handle As IntPtr, message As Integer) If Not midiOutShortMsg(handle, message) = MMSYSERR_NOERROR Then Throw New Exception("Send message failed") End Sub Public Shared Sub GetPlaybackDeviceCapabilities(handle As IntPtr, ByRef caps As MidiOutCaps, sizeOfmidiOutCaps As Integer) If Not midiOutGetDevCaps(handle, caps, sizeOfmidiOutCaps) = MMSYSERR_NOERROR Then Throw New Exception("Get device capabilities failed") End Sub Public Shared Function GetPlaybackDeviceCount() As Integer Dim result = midiOutGetNumDevs If Not result > MMSYSERR_NOERROR Then Throw New Exception("Get device count failed - no devices present") Return result End Function Protected Sub New() End Sub 'translated from: https://www.codeproject.com/articles/6228/c-midi-toolkit Public Structure MidiOutCaps ''' <summary> ''' Manufacturer identifier of the device driver for the Midi output ''' device. ''' </summary> Public ManufacturerId As Short ''' <summary> ''' Product identifier of the Midi output device. ''' </summary> Public ProductId As Short ''' <summary> ''' 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. ''' </summary> Public DriverVersion As Integer ''' <summary> ''' Product name string as ASCII byte array. ''' </summary> <MarshalAs(UnmanagedType.ByValArray, SizeConst:=32)> Public Name As Byte() ''' <summary> ''' Flags describing the type of the Midi output device. ''' </summary> Public Technology As Short ''' <summary> ''' 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. ''' </summary> Public VoiceCount As Short ''' <summary> ''' 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. ''' </summary> Public NoteCount As Short ''' <summary> ''' 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. ''' </summary> Public ChannelMask As Short ''' <summary> ''' Optional functionality supported by the device. ''' </summary> Public Support As Integer End Structure End Class
This is the primary code for working with winmm.dll and sending MIDI messages. You could play music with this code alone, but it will be easier to add some helper classes for sending messages and representing musical note data.
The MIDI messages can be wrapped in a simple 4-byte structure. Only three of the bytes are actually used to hold information, the fourth is simply to make an even 4 bytes for integer conversion.
<System.Runtime.InteropServices.StructLayout(Runtime.InteropServices.LayoutKind.Sequential)> Public Structure MidiShortMessage Private Const CHANNEL_MASK As Byte = &HF Private Const STATUS_MASK As Byte = &HF0 Private data0 As Byte Private data1 As Byte Private data2 As Byte Private data3 As Byte Public Sub New(command As MessageCommandMask, midiChannel As Byte, value1 As Byte, value2 As Byte) StatusCommand = command Channel = midiChannel Parameter1 = value1 Parameter2 = value2 End Sub Public Property StatusCommand As MessageCommandMask Get Return CType(data0 >> 4, MessageCommandMask) End Get Set(value As MessageCommandMask) data0 = value Or (data0 And CHANNEL_MASK) End Set End Property Public Property Channel As Byte Get Return (data0 And CHANNEL_MASK) End Get Set(value As Byte) data0 = (data0 And STATUS_MASK) Or (value And CHANNEL_MASK) End Set End Property Public Property Parameter1 As Byte Get Return data1 End Get Set(value As Byte) data1 = value End Set End Property Public Property Parameter2 As Byte Get Return data2 End Get Set(value As Byte) data2 = value End Set End Property Public Shared Widening Operator CType(target As MidiShortMessage) As Integer Return BitConverter.ToInt32({target.data0, target.data1, target.data2, target.data3}, 0) End Operator Public Enum MessageCommandMask As Byte NoteOff = &H80 NoteOn = &H90 PolyKeyPressure = &HA0 ControllerChange = &HB0 ProgramChange = &HC0 ChannelPressure = &HD0 PitchBend = &HD0 End Enum End Structure
This message structure is used to send command such as setting the voice for a channel or playing notes.
There will need to be a small class to encapsulate the MIDI playback device, holding its device id and information about its capabilities.
Public Class PlaybackDevice Public Shared Function DefaultDevice() As PlaybackDevice Dim caps As New NativeMethods.MidiOutCaps NativeMethods.GetPlaybackDeviceCapabilities(0, caps, NativeMethods.MidiOutCapsSize) Return New PlaybackDevice(0, caps) End Function Public Shared Function GetDevices() As IEnumerable(Of PlaybackDevice) Dim result As New List(Of PlaybackDevice) Dim deviceCount = NativeMethods.GetPlaybackDeviceCount For i = 0 To deviceCount - 1 Dim caps As New NativeMethods.MidiOutCaps NativeMethods.GetPlaybackDeviceCapabilities(i, caps, NativeMethods.MidiOutCapsSize) result.Add(New PlaybackDevice(i, caps)) Next Return result.ToArray End Function Public Property DeviceId As Integer Public Property DeviceName As String Public Property VoiceCount As Integer Public Property ChannelEnabled As IEnumerable(Of Boolean) Public Property NoteCount As Integer Public Sub New(id As Integer, caps As NativeMethods.MidiOutCaps) DeviceId = id DeviceName = Text.ASCIIEncoding.ASCII.GetString(caps.name) VoiceCount = caps.VoiceCount Dim channels As New List(Of Boolean) For i = 0 To 15 If (caps.ChannelMask And CShort(2 ^ i - 1)) > 0 Then channels.Add(True) Else channels.Add(False) End If Next NoteCount = caps.NoteCount ChannelEnabled = channels End Sub End Class
With those classes and structures in place, we are almost ready to write a MidiPlayer. But before we do, we'll need some objects and enums to represent the notes that we want to play, along with the instrument that will play them.
The instrument (or Voice) can simply be represented by an enum of voice Ids. The following list represents the default MIDI voice bank:
Public Enum InstrumentVoice As Byte Acoustic_Grand_Piano = 1 Bright_Acoustic_Piano = 2 Electric_Grand_Piano = 3 Honky_tonk_Piano = 4 Electric_Piano_1 = 5 Electric_Piano_2 = 6 Harpsichord = 7 Clavi = 8 Celesta = 9 Glockenspiel = 10 Music_Box = 11 Vibraphone = 12 Marimba = 13 Xylophone = 14 Tubular_Bells = 15 Dulcimer = 16 Drawbar_Organ = 17 Percussive_Organ = 18 Rock_Organ = 19 Church_Organ = 20 Reed_Organ = 21 Accordion = 22 Harmonica = 23 Tango_Accordion = 24 Acoustic_Guitar_nylon = 25 Acoustic_Guitar_steel = 26 Electric_Guitar_jazz = 27 Electric_Guitar_clean = 28 Electric_Guitar_muted = 29 Overdriven_Guitar = 30 Distortion_Guitar = 31 Guitar_harmonics = 32 Acoustic_Bass = 33 Electric_Bass_finger = 34 Electric_Bass_pick = 35 Fretless_Bass = 36 Slap_Bass_1 = 37 Slap_Bass_2 = 38 Synth_Bass_1 = 39 Synth_Bass_2 = 40 Violin = 41 Viola = 42 Cello = 43 Contrabass = 44 Tremolo_Strings = 45 Pizzicato_Strings = 46 Orchestral_Harp = 47 Timpani = 48 String_Ensemble_1 = 49 String_Ensemble_2 = 50 SynthStrings_1 = 51 SynthStrings_2 = 52 Choir_Aahs = 53 Voice_Oohs = 54 Synth_Voice = 55 Orchestra_Hit = 56 Trumpet = 57 Trombone = 58 Tuba = 59 Muted_Trumpet = 60 French_Horn = 61 Brass_Section = 62 SynthBrass_1 = 63 SynthBrass_2 = 64 Soprano_Sax = 65 Alto_Sax = 66 Tenor_Sax = 67 Baritone_Sax = 68 Oboe = 69 English_Horn = 70 Bassoon = 71 Clarinet = 72 Piccolo = 73 Flute = 74 Recorder = 75 Pan_Flute = 76 Blown_Bottle = 77 Shakuhachi = 78 Whistle = 79 Ocarina = 80 Lead_1_square = 81 Lead_2_sawtooth = 82 Lead_3_calliope = 83 Lead_4_chiff = 84 Lead_5_charang = 85 Lead_6_voice = 86 Lead_7_fifths = 87 Lead_8_bass_lead = 88 Pad_1_new_age = 89 Pad_2_warm = 90 Pad_3_polysynth = 91 Pad_4_choir = 92 Pad_5_bowed = 93 Pad_6_metallic = 94 Pad_7_halo = 95 Pad_8_sweep = 96 FX_1_rain = 97 FX_2_soundtrack = 98 FX_3_crystal = 99 FX_4_atmosphere = 100 FX_5_brightness = 101 FX_6_goblins = 102 FX_7_echoes = 103 FX_8_sci_fi = 104 Sitar = 105 Banjo = 106 Shamisen = 107 Koto = 108 Kalimba = 109 Bag_pipe = 110 Fiddle = 111 Shanai = 112 Tinkle_Bell = 113 Agogo = 114 Steel_Drums = 115 Woodblock = 116 Taiko_Drum = 117 Melodic_Tom = 118 Synth_Drum = 119 Reverse_Cymbal = 120 Guitar_Fret_Noise = 121 Breath_Noise = 122 Seashore = 123 Bird_Tweet = 124 Telephone_Ring = 125 Helicopter = 126 Applause = 127 Gunshot = 128 End Enum
Next we'll need a way to represent each of the chromatic notes across 8 octaves of a full piano keyboard. This can be done with another enum holding an encoded version of the note name along with the associated MIDI note code:
Public Enum NoteMidiCode As Byte Rest = 0 C8 = 108 B7 = 107 A7s = 106 A7 = 105 G7s = 104 G7 = 103 F7s = 102 F7 = 101 E7 = 100 D7s = 99 D7 = 98 C7s = 97 C7 = 96 B6 = 95 A6s = 94 A6 = 93 G6s = 92 G6 = 91 F6s = 90 F6 = 89 E6 = 88 D6s = 87 D6 = 86 C6s = 85 C6 = 84 B5 = 83 A5s = 82 A5 = 81 G5s = 80 G5 = 79 F5s = 78 F5 = 77 E5 = 76 D5s = 75 D5 = 74 C5s = 73 C5 = 72 B4 = 71 A4s = 70 A4 = 69 G4s = 68 G4 = 67 F4s = 66 F4 = 65 E4 = 64 D4s = 63 D4 = 62 C4s = 61 C4 = 60 B3 = 59 A3s = 58 A3 = 57 G3s = 56 G3 = 55 F3s = 54 F3 = 53 E3 = 52 D3s = 51 D3 = 50 C3s = 49 C3 = 48 B2 = 47 A2s = 46 A2 = 45 G2s = 44 G2 = 43 F2s = 42 F2 = 41 E2 = 40 D2s = 39 D2 = 38 C2s = 37 C2 = 36 B1 = 35 A1s = 34 A1 = 33 G1s = 32 G1 = 31 F1s = 30 F1 = 29 E1 = 28 D1s = 27 D1 = 26 C1s = 25 C1 = 24 B0 = 23 A0s = 22 A0 = 21 End Enum
Each note needs to play for a specified duration. The standard note durations are represented in the following enum:
Public Enum NoteDuration ThirtysecondthNode = 31 SixteenthNote = 62 EigthNote = 125 QuarterNote = 250 HalfNote = 500 WholeNote = 1000 End Enum
Finally we need a class to encapsulate a note, or notes, with a duration. We can define this class as a "chord":
Public Class Chord Public Property Notes As IEnumerable(Of NoteMidiCode) Public Property Duration As NoteDuration Public Property Velocity As Byte = 127 Public ReadOnly Property IsRest As Boolean Get If Notes?.Count = 1 AndAlso Notes.First = NoteMidiCode.Rest Then Return True Return False End Get End Property End Class
Now we can create a MidiPlayer class capable of opening a device and playing a series of chords on one or more channels.
Option Strict On Public Class MidiPlayer Implements IDisposable Public Event MidiMessageReceived As EventHandler(Of MidiMessageReceivedEventArgs) Public Property Device As PlaybackDevice Public ReadOnly Property IsOpen As Boolean Protected handle As Integer Private messageHandler As NativeMethods.MidiOutProc Public Sub New() Device = PlaybackDevice.DefaultDevice messageHandler = New NativeMethods.MidiOutProc(AddressOf OnMidiMessageReceived) End Sub Protected Overridable Sub OnMidiMessageReceived(handle As Integer, msg As Integer, instance As Integer, param1 As Integer, param2 As Integer) RaiseEvent MidiMessageReceived(Me, New MidiMessageReceivedEventArgs(handle, msg, instance, param1, param2)) End Sub Public Overridable Sub Close() If IsOpen Then NativeMethods.ResetPlaybackDevice(handle) NativeMethods.ClosePlaybackDevice(handle) _IsOpen = False End If End Sub Public Overridable Sub Open() NativeMethods.OpenPlaybackDevice(handle, Device.DeviceId, Nothing, 0, NativeMethods.CALLBACK_FUNCTION) _IsOpen = True End Sub Public Overridable Async Function Play(channel As Byte, chords As IEnumerable(Of Chord)) As Task If Device IsNot Nothing Then For Each chord In chords Try If Not chord.IsRest Then For i = 0 To chord.Notes.Count - 1 Dim note = chord.Notes.ElementAt(i) SendMessage(New MidiShortMessage(MidiShortMessage.MessageCommandMask.NoteOn, channel, note, chord.Velocity)) Next End If Await Task.Delay(chord.Duration) If Not chord.IsRest Then For i = 0 To chord.Notes.Count - 1 Dim note = chord.Notes.ElementAt(i) SendMessage(New MidiShortMessage(MidiShortMessage.MessageCommandMask.NoteOn, channel, note, 0)) Next End If Catch disposed_ex As ObjectDisposedException Exit For Catch ex As Exception Throw End Try Next End If End Function Public Overridable Async Function Play(channels As Dictionary(Of Byte, IEnumerable(Of Chord))) As Task If Device IsNot Nothing Then Dim tasks As New List(Of Task) For Each channel In channels tasks.Add(Task.Run(Async Function() Await Play(channel.Key, channel.Value) End Function)) Next Await Task.WhenAll(tasks) End If End Function Protected Overridable Sub SendMessage(message As MidiShortMessage) NativeMethods.SendPlaybackDeviceMessage(handle, message) End Sub Public Overridable Sub SetVoice(channel As Byte, voice As InstrumentVoice) SendMessage(New MidiShortMessage(MidiShortMessage.MessageCommandMask.ProgramChange, channel, voice, 0)) End Sub #Region "IDisposable Support" Private disposedValue As Boolean Protected Overridable Sub Dispose(disposing As Boolean) If Not disposedValue Then If disposing Then messageHandler = Nothing Close() End If End If disposedValue = True End Sub Public Sub Dispose() Implements IDisposable.Dispose Dispose(True) End Sub #End Region End Class Public Class MidiMessageReceivedEventArgs Inherits EventArgs Public Handle As Integer, Message As Integer, Instance As Integer, Param1 As Integer, Param2 As Integer Public Sub New(hnd As Integer, msg As Integer, inst As Integer, p1 As Integer, p2 As Integer) Handle = hnd Message = msg Instance = inst Param1 = p1 Param2 = p2 End Sub End Class
This implementation requires that all channels have the same number of notes. While this isn't necessarily conducive to real-world music composition, it can be accommodated with rest notes and careful adjustment of note durations. A more sophisticated player could implement a timeline by which to play each channel's notes.
To facilitate creating MIDI music from code, an additional helper class can be created to parse a string representation of music notes into MIDI data. There are any number of ways one might represent the notes, but this example uses upper case letters (CDEFGAB) followed by an optional # for sharps, followed by (whqest) for whole, half, quarter, eighth, sixteenth or thirty-seconds notes. Parts can be separated by whitespace or punctuation (,; etc). Chords can be combined using parentheses, for example (CEG) for C-major.
Public Class Tune Inherits ObjectModel.Collection(Of Chord) Public Overloads Sub Add(notes As IEnumerable(Of NoteMidiCode), duration As NoteDuration) Add(New Chord() With {.Notes = notes, .Duration = duration}) End Sub Public Shared Function Parse(tuneData As String) As Tune Dim result As New Tune Dim sb As New Text.StringBuilder Dim i As Integer Dim octave As Integer = 4 While i < tuneData.Length sb.Clear() If IsWhiteSpaceOrPunctuation(tuneData(i)) Then i += 1 : Continue While If Char.ToUpper(tuneData(i)) = "O"c Then i += 1 octave = Val(tuneData(i)) i += 1 End If If IsWhiteSpaceOrPunctuation(tuneData(i)) Then i += 1 : Continue While Dim tones As IEnumerable(Of NoteMidiCode) If tuneData(i) = "("c Then i += 1 Dim noteList As New List(Of NoteMidiCode) Dim lastNoteChar As Char = Chr(0) While Not tuneData(i) = ")"c If IsWhiteSpaceOrPunctuation(tuneData(i)) Then i += 1 Dim noteChar = Char.ToUpper(tuneData(i)) sb.Append(noteChar) If Not lastNoteChar = Chr(0) AndAlso noteChar > lastNoteChar Then sb.Append(octave.ToString) Else sb.Append((octave + 1).ToString) End If lastNoteChar = noteChar i += 1 If tuneData(i) = "#"c Then sb.Append("s") i += 1 End If noteList.Add(CType([Enum].Parse(GetType(NoteMidiCode), sb.ToString), NoteMidiCode)) sb.Clear() End While tones = noteList.ToArray i += 1 Else sb.Append(Char.ToUpper(tuneData(i))) i += 1 If Not sb.Chars(sb.Length - 1) = "R"c Then sb.Append(octave.ToString) If tuneData(i) = "#"c Then sb.Append("s") i += 1 End If Else sb.Append("est") End If tones = {CType([Enum].Parse(GetType(NoteMidiCode), sb.ToString), NoteMidiCode)} End If If IsWhiteSpaceOrPunctuation(tuneData(i)) Then i += 1 Dim dur As NoteDuration Select Case tuneData(i) Case "t"c dur = NoteDuration.ThirtysecondthNode Case "s"c dur = NoteDuration.SixteenthNote Case "e"c dur = NoteDuration.EigthNote Case "q"c dur = NoteDuration.QuarterNote Case "h"c dur = NoteDuration.HalfNote Case "w"c dur = NoteDuration.WholeNote End Select i += 1 result.Add(tones, dur) End While Return result End Function Private Shared Function IsWhiteSpaceOrPunctuation(c As Char) As Boolean If Char.IsWhiteSpace(c) Then Return True If Char.GetUnicodeCategory(c) = Globalization.UnicodeCategory.OtherPunctuation Then Return True Return False End Function End Class
And there we have it. The ability to play MIDI music from code with direct messages or interpreted strings of note data.
Here's an example of what a simple form using this code might look like:
Imports System.ComponentModel Public Class Form1 Dim midi As New SimpleMidiEngine.MidiPlayer Private Async Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click Button1.Enabled = False Dim score As New Dictionary(Of Byte, IEnumerable(Of SimpleMidiEngine.Chord)) score(1) = SimpleMidiEngine.Tune.Parse(RichTextBox1.Text) score(2) = score(1) Await midi.Play(score) Button1.Enabled = True End Sub Private Sub Form1_Closing(sender As Object, e As CancelEventArgs) Handles Me.Closing midi.Dispose() End Sub Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load For Each entry In [Enum].GetValues(GetType(SimpleMidiEngine.InstrumentVoice)) ComboBox1.Items.Add(entry) Next ComboBox1.SelectedIndex = 0 ComboBox2.SelectedIndex = 0 midi.Open() Label2.Text = $"Voices: {midi.Device.VoiceCount}, Notes: {midi.Device.NoteCount}" End Sub Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click midi.SetVoice(CInt(ComboBox2.SelectedItem), CType(ComboBox1.SelectedItem, SimpleMidiEngine.InstrumentVoice)) End Sub End Class
And a little ditty to play might look like:
O3 CqCqCqEeFw FqFqFqEeCw CqCqCqEqFqEqFh FqFqFqEeCw O5 GqFqGqFqGqFqEh EqEqEqDh O3 EqDqEqDqEqDqDqDh EqEqEqDqCw
Hopefully this example code provides a simple, solid foundation on which to build applications using simple MIDI music generation, or more complex MIDI device implementations.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
- Edited by Reed KimbleMVP, Moderator Saturday, March 18, 2017 12:18 AM updated nativemethods class
General discussion
All replies
-
-
Nice Reed. 8) Not to be picky but, i have one complaint that i noticed right away. All those handle types in the midixxx functions should be IntPtr types, not Integer types.
If you say it can`t be done then i`ll try it
Normally I would agree but the documentation specifically says not to cast the handle into a normal pointer - there is a special structure you are supposed to use but a normal integer seems to work happily.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
Normally I would agree but the documentation specifically says not to cast the handle into a normal pointer - there is a special structure you are supposed to use but a normal integer seems to work happily.
I am not arguing with you because i know you are usually right but, i don`t recall seeing that recommendation in the msdn documents for the midixxx functions, do you have a link that explains this that i can read through? I looked a bit last night but, could not find anything about it. The PInvoke Signature Toolkit shows them as IntPtr types and that is how i have always used them, as IntPtrs. 8)
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
If you say it can`t be done then i`ll try it
-
Its on the midioutmessage documentation for the handle parameter. It may not even apply to managed pointers, and I didn't test it to see if it would fail as indicated.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
You were right, IntPtr was the way to go, I've modified the code and it works fine.
I've also updated the Tune class to fix a bug in the chord parsing (forgot to account for octave shifts in chords).
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
I was starting to think you where right after finding that the PInvoke Signature Toolkit does have a listing for the HMIDIOUT / LPHMIDIOUT structure which appears as below.
<StructLayout(LayoutKind.Sequential)> Public Structure HMIDIOUT__ Public unused As Integer End Structure
However, there does not seem to be an HMIDIOUT or LPHMIDIOUT structure to be found anywhere in the msdn documents, or at least not that i could find. Nor is there any real explanation anywhere in the msdn documents as to exactly what type(s) it is or contains.
I was looking at the midiOutOpen function that has the lphmo parameter which indicates it is a pointer to an HMIDIOUT handle that is used by the rest of the midixxx functions, along with another parameter, the uDeviceID parameter which is an unsigned Integer type that indicates it is an identifier of the midi device.
My best guess was that the midiOutOpen function creates the "HMIDIOUT" structure in the memory and then just passes back a handle to that structure in the lphmo parameter. Does that sound right to you?
If you say it can`t be done then i`ll try it
- Edited by IronRazerz Saturday, March 18, 2017 10:14 AM
-
The topic of working with MIDI files from VB.Net comes up from time to time
@Reed,
Once in the 10 years? Despite of that I find it helpful.
:-)
Success
Cor- Edited by Cor Ligthert Saturday, March 18, 2017 2:01 PM
-
Well, the midiOutOpen function expects a pointer to a HMIDIOUT handle so I have to assume there was a way to create one in the first place, and the midiOutOpen function just points it the proper handle instance. I guess that the managed pointer we use in .Net automagically represents whatever pointer type is assigned to it.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
If Notes?.Count = 1 AndAlso Notes.First = NoteMidiCode.Rest Then Return True
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
Don't forget to vote for Helpful Posts and Mark Answers!
*This post does not reflect the opinion of Microsoft, or its employees. -
That's the Elvis operator (null-conditional operator). If Notes is Nothing, attempting to read the .Count property won't throw an exception. Instead the comparison will just be false.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
That's the Elvis operator (null-conditional operator). If Notes is Nothing, attempting to read the .Count property won't throw an exception. Instead the comparison will just be false.
Omg and all this time I've been doing it the long way...
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
Don't forget to vote for Helpful Posts and Mark Answers!
*This post does not reflect the opinion of Microsoft, or its employees. -
That's the Elvis operator (null-conditional operator). If Notes is Nothing, attempting to read the .Count property won't throw an exception. Instead the comparison will just be false.
Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
Omg and all this time I've been doing it the long way...
Don't forget to vote for Helpful Posts and Mark Answers!
*This post does not reflect the opinion of Microsoft, or its employees.
Don't feel bad, this has only been around for a couple of years. Came when the C#7 stuff was released.Reed Kimble - "When you do things right, people won't be sure you've done anything at all"
-
Don't feel bad, this has only been around for a couple of years. Came when the C#7 stuff was released.
Don't forget to vote for Helpful Posts and Mark Answers!
*This post does not reflect the opinion of Microsoft, or its employees.