A “Play” Command for Visual Basic

By | 2002-06-01

This is the equivalent of the QBasic PLAY command that enabled you to play notes through the PC speaker. This version allows you to take advantage of the Sound card and therefore has many advantages. It uses the MIDI interface to send individual or multiple notes to the sound card.

Original Author: Aidan

Inputs

It requires the notes that you wish to play – See the code for the
exact syntax.

Assumptions

The notes available range from A-G

Returns

Nothing

Side Effects

If you end the program while it is playing, it may not function
correctly until you next restart windows (or log off and log on again).

API Declarations

Private Declare Sub SleepAPI Lib “kernel32” Alias “Sleep”_
(ByVal dwMilliseconds As Long)
Private Declare Function midiOutOpen Lib “winmm.dll”_
(lphMidiOut As Long, ByVal uDeviceID As Long, ByVal_
dwCallback As Long, ByVal dwInstance As Long,_
ByVal dwflags As Long) As Long
Private Declare Function midiOutShortMsg Lib “winmm.dll”_
(ByVal hMidiOut As Long, ByVal dwMsg As Long) As Long
Private Declare Function midiOutClose Lib “winmm.dll”_
(ByVal hMidiOut As Long) As Long
Private Declare Function midiOutReset Lib “winmm.dll”_
(ByVal hMidiOut As Long) As Long

Code

' Place a Textbox (Text1) and a Command Button (Command1)
' on the Form
' The following code should be placed in Form1:
' This code gives the Visual Basic equivalent of the QBasic
' PLAY command. A few extra options have been added (such as
' playing several notes simultaneously).
' I have found it difficult to stop the notes after playing them
' You can try this out by using the MN switch.
' If anyone knows how to do this, please E-Mail me at
' aidanx@yahoo.com

' Constants
Private Const Style_Normal = 0
Private Const Style_Staccato = 1
Private Const Style_Legato = 2
Private Const Style_Sustained = 3
Private Const PlayState_Disable = 0
Private Const PlayState_Enable = 1
Private Const PlayState_Auto = -1
' Types
Private Type Note
  Pitch As Long
  Length As Integer
  Volume As Long
  Style As Integer
End Type
' Variables
Private MIDIDevice As Long

Private Sub Command1_Click()
  ' The notes in the text box are played when the button
  ' is pressed
  Play Text1.Text
End Sub
Private Sub Form_Load()
  Text1.Text = "cdecdefgafga"
  Play "MDO3cdefgabO4c"
End Sub

Public Sub Play(Notes As String)
  ' Plays a note(s) using MIDI
  ' E.g. Play "T96O3L4cd.efgabO4cL8defgabO5c"
  
  ' Note Letter - Plays Note (C is lowest in an octave, B is highest)
  ' L + NoteLength (4 = Crotchet, 2 = Minim, etc.,
' 0 = Play Simultaneously)
  ' N + Note Number (37 = Mid C, 38 = C#, etc.)
  ' O + Octave No. (3 = Middle - i.e. O3C = Mid. C)
  ' P + Length (Pause of Length - See "L" -
' Without a Number = Current Note Length)
  ' T + Tempo (Crotchet Beats per Minute)
  ' V + VolumeConstant (F = Forte, O = Mezzo-Forte,
' I = MezzoPiano, P = Piano)
  ' M + Music Style Constant (S = Staccato, N = Normal,
' L = Legato, D = Sustained)
      ' Only the Sustained style appears to function
' correctly as the time taken to stop a midi note
' is not negligible
  ' If ommitted, uses last set option
  
  Dim CurrentNote As Long, PauseNoteLength
  Dim i As Long, LenStr As String
  Dim Note(6) As Integer, Sharp As Integer
  Dim NoteCaps As String, NoteASCII As Integer, _
PlayLength As Double
  Dim PlayNote() As Note
  
  Static NotFirstRun As Boolean ' Set to True if
' it is not the first time Play has been called
  Static Octave As Integer, Tempo As Integer, _
CurrentNoteLength As Integer, CurrentVolume As Integer, _
MusicStyle As Integer
  
  ' Enable MIDI
  If Not EnablePlay(PlayState_Enable) Then Exit Sub
  If Not NotFirstRun Then
    NotFirstRun = True
    Octave = 3
    Tempo = 120
    CurrentVolume = 96
    CurrentNoteLength = 4
    MusicStyle = Style_Sustained
  End If
  ' Notes
    Note(0) = 9   ' A
    Note(1) = 11  ' B
    Note(2) = 0   ' C
    Note(3) = 2   ' D
    Note(4) = 4   ' E
    Note(5) = 5   ' F
    Note(6) = 7   ' G
  ' End Notes
  NoteCaps = UCase$(Notes)
  CurrentNote = -1
  i = 0
  Do Until i = Len(NoteCaps)
    i = i + 1
    NoteASCII = Asc(Mid$(NoteCaps, i, 1))
    If Chr$(NoteASCII) = "N" Then
      ' Play Note by Number
      LenStr = ""
      Do Until i = Len(NoteCaps) Or _
Val(Mid$(NoteCaps, i + 1, 1)) = 0
        LenStr = LenStr + Mid$(NoteCaps, i + 1, 1)
        i = i + 1
      Loop
      If LenStr <> "" Then
        CurrentNote = CurrentNote + 1
        ReDim Preserve PlayNote(CurrentNote)
        If Val(LenStr) <> 0 Then
          PlayNote(CurrentNote).Pitch = Val(LenStr) + 23
        Else
          PlayNote(CurrentNote).Pitch = -1
        End If
        PlayNote(CurrentNote).Length = CurrentNoteLength
        PlayNote(CurrentNote).Volume = CurrentVolume
        PlayNote(CurrentNote).Style = MusicStyle
      End If
    End If
    If NoteASCII >= 0 Then
      If Chr$(NoteASCII) = "T" Then
        ' Set Tempo
        LenStr = ""
        Do Until i = Len(NoteCaps) Or _
Val(Mid$(NoteCaps, i + 1, 1)) = 0
          LenStr = LenStr + Mid$(NoteCaps, i + 1, 1)
          i = i + 1
        Loop
        If LenStr <> "" Then
          Tempo = Val(LenStr)
        End If
      End If
    
      If Chr$(NoteASCII) = "." And CurrentNote >= 0 Then
        ' Make last note length 3/2 times as long
        PlayNote(CurrentNote).Length = _
PlayNote(CurrentNote).Length / 1.5
      End If
      
      If Chr$(NoteASCII) = "P" Then
        ' Pause
        LenStr = ""
        Do Until i = Len(NoteCaps) Or _
Val(Mid$(NoteCaps, i + 1, 1)) = 0
          LenStr = LenStr + Mid$(NoteCaps, i + 1, 1)
          i = i + 1
        Loop
        NoteASCII = -1
        If LenStr <> "" Then
          PauseNoteLength = Val(LenStr)
        Else
          PauseNoteLength = CurrentNoteLength
        End If
      End If
    
      If Chr$(NoteASCII) = "L" Then
        ' Set Length
        LenStr = ""
        Do Until i = Len(NoteCaps) Or _
(Val(Mid$(NoteCaps, i + 1, 1)) = 0 And _
Mid$(NoteCaps, i + 1, 1) <> "0")
          LenStr = LenStr + Mid$(NoteCaps, i + 1, 1)
          i = i + 1
        Loop
        If LenStr <> "" Then
          CurrentNoteLength = Val(LenStr)
        End If
      End If
            
      If Chr$(NoteASCII) = "O" Then
        ' Set Octave
        If i < Len(NoteCaps) Then
          NoteASCII = Asc(Mid$(NoteCaps, i + 1, 1))
          If NoteASCII > 47 And NoteASCII < 55 Then
            Octave = NoteASCII - 48
            i = i + 1
          End If
        End If
      End If
    End If
    If (NoteASCII > 64 And NoteASCII < 73) Or NoteASCII = -1 Then
      ' Select Note
      Sharp = 0
      If NoteASCII <> -1 Then
        If i < Len(NoteCaps) Then
          If Mid$(NoteCaps, i + 1, 1) = "#" Or _
Mid$(NoteCaps, i + 1, 1) = "+" Then
            i = i + 1
            Sharp = 1
          ElseIf Mid$(NoteCaps, i + 1, 1) = "-" Then
            i = i + 1
            Sharp = -1
          End If
        End If
      End If
      CurrentNote = CurrentNote + 1
      ReDim Preserve PlayNote(CurrentNote)
      If NoteASCII <> -1 Then
        PlayNote(CurrentNote).Pitch = (Octave * 12) + _
Note(NoteASCII - 65) + Sharp + 24
        PlayNote(CurrentNote).Length = CurrentNoteLength
      Else
        PlayNote(CurrentNote).Pitch = -1
        PlayNote(CurrentNote).Length = PauseNoteLength
      End If
      PlayNote(CurrentNote).Volume = CurrentVolume
      PlayNote(CurrentNote).Style = MusicStyle
    End If
    If NoteASCII > -1 Then
      If Chr$(NoteASCII) = "V" Then
        ' Set Volume
        If i < Len(NoteCaps) Then
          i = i + 1
          Select Case Mid$(NoteCaps, i, 1)
          Case "F"  ' Forte
            CurrentVolume = 127
          Case "O"  ' Mezzo-Forte
            CurrentVolume = 96
          Case "I"  ' Mezzo-Piano
            CurrentVolume = 65
          Case "P"  ' Piano
            CurrentVolume = 34
          Case Else
            i = i - 1
          End Select
        End If
      End If
      If Chr$(NoteASCII) = "M" Then
        ' Set Music Style
        If i < Len(NoteCaps) Then
          i = i + 1
          Select Case Mid$(NoteCaps, i, 1)
          Case "S"  ' Staccato
            MusicStyle = Style_Staccato
          Case "N"  ' Normal
            MusicStyle = Style_Normal
          Case "L"  ' Legato
            MusicStyle = Style_Legato
          Case "D"  ' Sustained
            MusicStyle = Style_Sustained
          Case Else
            i = i - 1
          End Select
        End If
      End If
    End If
  Loop
  ' Play Notes
  For i = 0 To CurrentNote
    ' Send Note
    If PlayNote(i).Pitch <> -1 Then SendMidiOut 144, _
PlayNote(i).Pitch, PlayNote(i).Volume
    ' Wait until next note should be played
    If i < CurrentNote Then
      PlayLength = ((((60 / Tempo) * 4) * (1 / _
PlayNote(i).Length)) * 1000)
      If PlayNote(i).Length > 0 Then
        Select Case PlayNote(i).Style
        Case Style_Sustained
          ' Play the full note value and don't stop it
  ' afterwards
          SleepAPI Int(PlayLength + 0.5)
        Case Style_Normal
          ' Play 7/8 of the note value
          SleepAPI Int(PlayLength * (7 / 8) + 0.5)
          Call midiOutReset(MIDIDevice)
          SleepAPI Int((PlayLength * (1 / 8)) + 0.5)
        Case Style_Legato
          ' Play the full note value
          SleepAPI Int(PlayLength + 0.5)
          Call midiOutReset(MIDIDevice)
          SleepAPI 1
        Case Style_Staccato
          ' Play half the note value and pause for
  ' the remainder
          SleepAPI Int(PlayLength * (1 / 2) + 0.5)
          Call midiOutReset(MIDIDevice)
          SleepAPI Int((PlayLength * (1 / 2)) + 0.5)
        End Select
      End If
    End If
    DoEvents
  Next i
  SleepAPI 1   ' This must be done in order for the last
  ' note to be played
  ' Disable MIDI
  Call EnablePlay(PlayState_Disable)
End Sub
Private Function EnablePlay(Enable As Integer) As Boolean
  ' Enables/Disables MIDI Playing
  ' Enable = PlayState_?
  Dim MIDIOut As Long, ReturnValue As Long
  Static MIDIEnabled As Boolean
  
  If (Enable <> PlayState_Disable) And MIDIEnabled = False Then
    ' Enable MIDI
    ReturnValue = midiOutOpen(MIDIOut, -1, 0&, 0&, 0&)
    If ReturnValue = 0 Then
      MIDIEnabled = True
      EnablePlay = True
      MIDIDevice = MIDIOut
    Else
      EnablePlay = False
    End If
  ElseIf (Enable <> PlayState_Enable) And MIDIEnabled = True Then
    ' Disable MIDI
    ReturnValue = midiOutClose(MIDIDevice)
    If ReturnValue = 0 Then
      MIDIEnabled = False
      EnablePlay = True
    Else
      EnablePlay = False
    End If
  End If
End Function
Private Sub SendMidiOut(MidiEventOut As Long, MidiNoteOut As Long,_
MidiVelOut As Long)
  ' Sends the Note to the MIDI Device
  Dim LowInt As Long, VelOut As Long, HighInt As Long,_
MIDIMessage As Long
  Dim ReturnValue As Long
  LowInt = (MidiNoteOut * 256) + MidiEventOut
  VelOut = MidiVelOut * 256
  HighInt = VelOut * 256
  MIDIMessage = LowInt + HighInt
  ReturnValue = midiOutShortMsg(MIDIDevice, MIDIMessage)
End Sub

Author: dwirch

Derek Wirch is a seasoned IT professional with an impressive career dating back to 1986. He brings a wealth of knowledge and hands-on experience that is invaluable to those embarking on their journey in the tech industry.

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.