Bay Six Software Forum Index Bay Six Software
Beyond the Basics
 
 FAQFAQ   SearchSearch   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 

MusicLib

 
Post new topic   Reply to topic    Bay Six Software Forum Index -> General Discussions
View previous topic :: View next topic  
Author Message
JosephE
Junior Member


Joined: 01 Nov 2007
Posts: 21

PostPosted: Aug 3rd, 2009, 4:29am    Post subject: MusicLib Reply with quote

I've been working on a basic MIDI output library called MusicLib for LB.

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
Back to top
View user's profile Send private message
Brent
Site Admin


Joined: 01 Jul 2005
Posts: 800

PostPosted: Aug 3rd, 2009, 9:16pm    Post subject: Re: MusicLib Reply with quote

Hi Joseph,

It looks pretty good so far.

Personally, I would refrain from using strings for error handling. The users of your library might want errors in their own language--not necessarily English.

Also, requiring the library's users to pass a variable to every procedure could annoy them. It is much more common to return error codes, but the setting of a global variable is also pretty common. If I do the latter, I usually wrap it in a function, similar to the way the GetLastError API in Windows.

BTW, have you looked at the QBasic PLAY Challenge from a few years ago?
http://libertybasic.conforums.com/index.cgi?board=contests&action=display&num=1075263936

_________________
Brent
Back to top
View user's profile Send private message Send e-mail
JosephE
Junior Member


Joined: 01 Nov 2007
Posts: 21

PostPosted: Aug 4th, 2009, 9:56pm    Post subject: Re: MusicLib Reply with quote

Hmm...Those are good thoughts. I'll have to smooth it out a little bit.

That gets me thinking...
Back to top
View user's profile Send private message
JosephE
Junior Member


Joined: 01 Nov 2007
Posts: 21

PostPosted: Aug 27th, 2009, 3:12pm    Post subject: Re: MusicLib Reply with quote

You know, I actually have looked at the QBASIC Play challenge. However, my idea was to create music on the fly (slightly lower level than playing a string of data). Although that has it's ups and downs.

What you said about error handling makes a lot of sense. I really should go back over it and add a global variable (as you mentioned) for error handling. That would smooth things out a bit.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Bay Six Software Forum Index -> General Discussions All times are GMT
Page 1 of 1
Jump to:  
Quick Reply
Username:
Message:
   Shortcut keys: Alt+Q to activate, Alt+P to preview, Alt+S to submit
You can post new topics in this forum
You can reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You cannot download files in this forum



Lo-Fi Version
Powered by phpBB © 2001, 2005 phpBB Group