Since Brent's way of writing functions has really influenced me, I thought I might post what I have so far here. Critiques, addons, suggestions - all our welcome.
Basically, to play a note, you specify the letter of the note:
A, B, C, D, E, F, or G
All notes are measured from middle c. To specify a note in a different octave, use the + and - signs.
For example, ++E plays an E note two octaves above the middle c.
-F plays a an F note an octave below middle c.
To sharp a note (raise it a half-step), use the hash sign. To flat a note (lower it a half-step), use the percent sign.
+C# - plays the note c sharp an octave above middle c.
--G% - plays the note g flat two octaves below middle c.
My wrapper includes a lot of error checking. But it helps to know what went wrong.
Sources include: MSDN, http://www.computermusicresource.com/MIDI.Commands.html, http://www.harmony-central.com/MIDI/Doc/table2.html
Code with a very tiny example:
Code: |
'--------------------------------------------------------------------------------------------'
'Filename: MusicLib.bas 'Description: Legacy Midi Input Wrapper for Liberty BASIC 'File Creation: Saturday, August 1st, 2009 'Author: Joseph Essin '--------------------------------------------------------------------------------------------' hMidi = Music.Open(e$) If e$<>"" Then Print e$ 'Set our instrument to #50 - strings. Call Music.SetInstrument e$, hMidi, 1, 50 If e$<>"" Then Print e$ 'Play D flat on the octave above middle c. Note$ = Music.Note.Play$(e$, hMidi, 1, "+D%", 127) If e$<>"" Then Print e$ 'Stop our note after a delay of 2 and a half seconds. CallDLL #kernel32, "Sleep",2500 As Long, Null As Void Call Music.Note.Stop e$, Note$ If e$<>"" Then Print e$ 'Give it time to let the note fade out (now that we have turned it off): CallDLL #kernel32, "Sleep",1000 As Long, Null As Void 'Close our midi output device: Call Music.Close e$, hMidi Print "Finished." If e$<>"" Then Print e$ End Function Music.Open(ByRef Error$) 'Parameters: 'Error$ - this holds the description of any errors that occur. ' Error$ is untouched if no errors occur. '----------------------------' 'CONSTANTS: (From mmsystem.h) MMSYSERR.NOERROR = 0 MMSYSERR.BASE = 0 MIDIERR.BASE = 64 MIDIERR.NODEVICE = MIDIERR.BASE + 4 MMSYSERR.ALLOCATED = MMSYSERR.BASE + 4 MMSYSERR.BADDEVICEID = MMSYSERR.BASE + 2 MMSYSERR.INVALPARAM = MMSYSERR.BASE + 11 MMSYSERR.NOMEM = MMSYSERR.BASE + 7 MIDI.MAPPER = -1 '---------------------------------------------' Error$ = "" uDeviceID = MIDI.MAPPER Struct HMIDIOUT, lphmo$ As PTR CallDLL #winmm, "midiOutOpen",_ HMIDIOUT As Struct,_ uDeviceID As Long,_ dwCallback As uLong,_ dwCallbackInstance As uLong,_ dwFlags As uLong,_ MMRESULT As uLong Select Case MMRESULT Case MIDIERR.NODEVICE : Error$ = "No MIDI port was found. This error occurs only when the mapper is opened." Case MMSYSERR.ALLOCATED : Error$ = "The specified resource is already allocated." Case MMSYSERR.BADDEVICEID : Error$ = "The specified device identifier is out of range." Case MMSYSERR.INVALPARAM : Error$ = "The specified pointer or structure is invalid." Case MMSYSERR.NOMEM : Error$ = "The system is unable to allocate or lock memory." Case MMSYSERR.NOERROR Music.Open = HMIDIOUT.lphmo$.struct 'Return the handle to the opened midi output if successful. End Select End Function Sub Music.Close ByRef Error$, hmo 'Parameters: 'Error$ - this holds the description of any errors that occur. ' Error$ is untouched if no errors occur. 'hmo - handle to the midi output obtained from Midi.Open() '----------------------------' 'CONSTANTS: (From mmsystem.h) MMSYSERR.BASE = 0 MIDIERR.BASE = 64 MMSYSERR.NOERROR = 0 MMSYSERR.INVALHANDLE = MMSYSERR.BASE + 5 MMSYSERR.NOMEM = MMSYSERR.BASE + 7 MIDIERR.STILLPLAYING = MIDIERR.BASE + 1 '---------------------------------------------' Error$ = "" 'Reset the midi device before we close it (to avoid errors): '(this turns off all the notes that are playing if there are any) CallDLL #winmm, "midiOutReset",_ hmo As uLong,_ MMRESULT As uLong If MMRESULT = MMSYSERR.INVALHANDLE Then 'Trap the one and only error that 'midiOutReset can produce: Error$ = "The specified device handle is invalid." Exit Sub End If CallDLL #winmm, "midiOutClose",_ hmo As uLong,_ MMRESULT As uLong Select Case MMRESULT 'Check for possible errors from midiOutClose: Case MIDIERR.STILLPLAYING : Error$ = "Buffers are still in the queue." Case MMSYSERR.INVALHANDLE : Error$ = "The specified device handle is invalid." Case MMSYSERR.NOMEM : Error$ = "The system is unable to load mapper string description." End Select End Sub Function Music.Note.Play$(ByRef Error$, hmo, Channel, Note$, VelocityOn) 'Parameters: 'Error$ - this holds the description of any errors that occur. ' Error$ is untouched if no errors occur. 'hmo - handle to the midi output obtained from Midi.Open() 'Channel - A number from 1-16 of the channel you want the note to play on. 'Note$ - A string containing the notation of the note you want to play: ' example: ++F# -< This plays the note f-sharp two octaves above the middle c scale ' example: C <- this plays middle c ' example: -E% <- this plays the note e-flat an octave below the middle c scale 'VelocityOn - how hard and loud the note plays. Must be 0-127 'Music.Note.Play$ returns the handle to a playing note that is required by Music.Note.Stop() '----------------------------' 'Out of range exceptions: If Channel<1 Or Channel>16 Then Error$ = "MIDI Channel #";Channel;" is out of range. Use 1-16." Exit Function End If If VelocityOn <0 Or VelocityOn>127 Then Error$ = "VelocityOn #";VelocityOn; " is out of range. Use 0-127." Exit Function End If 'CONSTANTS: (From mmsystem.h) MMSYSERR.NOERROR = 0 MMSYSERR.BASE = 0 MIDIERR.BASE = 64 MIDIERR.BADOPENMODE = MIDIERR.BASE + 6 MIDIERR.NOTREADY = MIDIERR.BASE + 3 MMSYSERR.INVALHANDLE = MMSYSERR.BASE + 5 '---------------------------------------------' Error$ = "" Note$ = Lower$(Note$) NoteNumbers$ = "60 61 62 63 64 65 66 67 68 69 70 71" Notes$ = Lower$("C C# D D# E F F# G G# A A# B") Status = 143 + Channel 'Turn the note on. NoteNumber = 0 OctaveOffset = 0 SemitoneOffset = 0 DidPickNote = 0 For c = 1 To Len(Note$) char$ = Mid$(Note$,c,1) Select Case char$ Case "c","d","e","f","g","a","b" For w = 1 To 12 If char$ = Word$(Notes$,w) Then DidPickNote = 1 NoteNumber = Val(Word$(NoteNumbers$,w)) Exit For End If Next w Case "#" 'sharp SemitoneOffset = SemitoneOffset + 1 Case "%" 'flat SemitoneOffset = SemitoneOffset - 1 Case "+" OctaveOffset = OctaveOffset + 12 Case "-" OctaveOffset = OctaveOffset - 12 End Select Next c If Not(DidPickNote) Then 'Failure to specify what note... Error$ = "A note letter must be specified. {A, B, C, D, E, F, G}" Exit Function End If 'Now calculate the final value of the note: NoteToPlay = NoteNumber + SemitoneOffset + OctaveOffset If NoteToPlay > 127 Or NoteToPlay < 0 Then 'Note out of range exception: Error$ = "The value ";NoteToPlay;" is out of range. Note values for the Win32 API must be 0-127." Exit Function End If 'Let's package all the info for the note together to send it to windows. LowByte = (NoteToPlay * 256) + Status HighByte = VelocityOn * 256 * 256 dwMsg = LowByte+HighByte CallDLL #winmm, "midiOutShortMsg",_ hmo As uLong,_ 'Midi Output Handle dwMsg As uLong,_ 'What note to play and how to play it all packed together. MMRESULT As uLong 'Finally, let's check to see if the midiOutShortMsg produced an error: Select Case MMRESULT Case MIDIERR.BADOPENMODE : Error$ = "The application sent a message without a status byte to a stream handle." Case MIDIERR.NOTREADY : Error$ = "The hardware is busy with other data." Case MMSYSERR.INVALHANDLE : Error$ = "The specified device handle is invalid." Case MMSYSERR.NOERROR Music.Note.Play$ = Str$(hmo) + "_" + Str$(Channel) + "_" + Str$(NoteToPlay) + "_" + Str$(VelocityOn) End Select End Function Sub Music.Note.Stop ByRef Error$, ByRef hNote$ 'Parameters: 'Error$ - this holds the description of any errors that occur. ' Error$ is untouched if no errors occur. 'hNote$ - this is the handle to a note returned by Music.Note.Play$() '----------------------------' Error$ = "" If Trim$(hNote$)="" Then Error$ = "hNote$ must be a valid handle to a note returned by Music.Note.Play()." Exit Sub End If hmo = Val(Word$(hNote$,1,"_")) Channel = Val(Word$(hNote$,2,"_")) Note = Val(Word$(hNote$,3,"_")) Velocity = Val(Word$(hNote$,4,"_")) 'Out of range exceptions: If Channel<1 Or Channel>16 Then Error$ = "MIDI Channel #";Channel;" is out of range. Use 1-16." Exit Sub End If If Velocity <0 Or Velocity>127 Then Error$ = "Velocity #";Velocity; " is out of range. Use 0-127." Exit Sub End If If Note > 127 Or Note < 0 Then Error$ = "The Win32 API value of the note must be from 0-127." Exit Sub End If 'CONSTANTS: (From mmsystem.h) MMSYSERR.NOERROR = 0 MMSYSERR.BASE = 0 MIDIERR.BASE = 64 MIDIERR.BADOPENMODE = MIDIERR.BASE + 6 MIDIERR.NOTREADY = MIDIERR.BASE + 3 MMSYSERR.INVALHANDLE = MMSYSERR.BASE + 5 '---------------------------------------------' Status = 127 + Channel 'Turn off note LowByte = (Note * 256) + Status HighByte = Velocity * 256 * 256 dwMsg = LowByte+HighByte CallDLL #winmm, "midiOutShortMsg",_ hmo As uLong,_ dwMsg As uLong,_ MMRESULT As uLong Select Case MMRESULT Case MIDIERR.BADOPENMODE : Error$ = "The application sent a message without a status byte to a stream handle." Case MIDIERR.NOTREADY : Error$ = "The hardware is busy with other data." Case MMSYSERR.INVALHANDLE : Error$ = "The specified device handle is invalid." Case MMSYSERR.NOERROR : hNote$ = "" 'nullify the handle to the note. End Select End Sub Sub Music.SetInstrument ByRef Error$, hmo, Channel, Instrument 'Parameters: 'Error$ - this holds the description of any errors that occur. ' Error$ is untouched if no errors occur. 'hmo - handle to the midi output obtained from Midi.Open() 'Channel - A number from 1-16 specifying the channel. 'Instrument - A number from 1-128 that specifies which instrument/sound to ' use. '----------------------------' Error$ = "" Instrument = Instrument - 1 'Out of range exceptions: If Channel<1 Or Channel>16 Then Error$ = "MIDI Channel #";Channel;" is out of range. Use 1-16." Exit Sub End If If Instrument>127 Or Instrument<0 Then Error$ = "Instrument is out of range. Please specify a number from 1-128." Exit Sub End If 'CONSTANTS: (From mmsystem.h) MMSYSERR.NOERROR = 0 MMSYSERR.BASE = 0 MIDIERR.BASE = 64 MIDIERR.BADOPENMODE = MIDIERR.BASE + 6 MIDIERR.NOTREADY = MIDIERR.BASE + 3 MMSYSERR.INVALHANDLE = MMSYSERR.BASE + 5 '---------------------------------------------' Status = 191 + Channel 'Change instrument LowByte = (Instrument * 256) + Status HighByte = 127 * 256 * 256 dwMsg = LowByte+HighByte CallDLL #winmm, "midiOutShortMsg",_ hmo As uLong,_ dwMsg As uLong,_ MMRESULT As uLong Select Case MMRESULT Case MIDIERR.BADOPENMODE : Error$ = "The application sent a message without a status byte to a stream handle." Case MIDIERR.NOTREADY : Error$ = "The hardware is busy with other data." Case MMSYSERR.INVALHANDLE : Error$ = "The specified device handle is invalid." End Select End Sub |