Yesterday, 08:05 PM
Just now I thought I'd ask Google Gemini and actually got an answer!
I haven't had a chance to run it (not at the computer with a MIDI keyboard)
but here's what it came up with...
Send MIDI notes + pitch wheel to keyboard
Prompt: Write code in QB64PE 4.0.0 running on Windows 11 to send MIDI notes and control the pitch wheel to a real MIDI keyboard plugged into the PC.
Code snippet
Explanation and Key Differences (QB64PE):
Read MIDI notes + pitch wheel from keyboard
Prompt: Write code in QB64PE 4.0.0 running on Windows 11 to read MIDI notes and the pitch wheel from a real MIDI keyboard plugged into the PC.
Code snippet
Key Changes and Explanations for QB64PE:
I haven't had a chance to run it (not at the computer with a MIDI keyboard)
but here's what it came up with...
Send MIDI notes + pitch wheel to keyboard
Prompt: Write code in QB64PE 4.0.0 running on Windows 11 to send MIDI notes and control the pitch wheel to a real MIDI keyboard plugged into the PC.
Code snippet
Code: (Select All)
DECLARE LIBRARY "winmm.dll"
FUNCTION midiOutOpen& (lphMidiOut&, uDeviceID&, dwCallback&, dwCallbackInstance&, dwFlags&)
FUNCTION midiOutShortMsg& (hMidiOut&, dwMsg&)
FUNCTION midiOutClose& (hMidiOut&)
END DECLARE LIBRARY
CONST MIDI_MAPPER = -1
CONST MIDI_NOTEON = &H90
CONST MIDI_NOTEOFF = &H80
CONST MIDI_PITCHWHEEL = &HE0
DIM midiHandle&
SUB OpenMidi
DIM result&
result& = midiOutOpen&(midiHandle&, MIDI_MAPPER, 0, 0, 0)
IF result& <> 0 THEN
PRINT "Could not open MIDI device."
END
END IF
END SUB
SUB CloseMidi
IF midiHandle& <> 0 THEN
midiOutClose& midiHandle&
midiHandle& = 0
END IF
END SUB
SUB SendMidiNote (channel%, note%, velocity%)
DIM msg&
IF midiHandle& = 0 THEN
PRINT "MIDI device not open."
EXIT SUB
END IF
' Note On
msg& = velocity% * 65536 + note% * 256 + (MIDI_NOTEON OR channel%)
midiOutShortMsg& midiHandle&, msg&
END SUB
SUB SendMidiNoteOff (channel%, note%, velocity%)
DIM msg&
IF midiHandle& = 0 THEN
PRINT "MIDI device not open."
EXIT SUB
END IF
' Note Off
msg& = velocity% * 65536 + note% * 256 + (MIDI_NOTEOFF OR channel%)
midiOutShortMsg& midiHandle&, msg&
END SUB
SUB SendPitchWheel (channel%, pitchWheelValue%)
DIM lsb%, msb%
DIM msg&
IF midiHandle& = 0 THEN
PRINT "MIDI device not open."
EXIT SUB
END IF
lsb% = pitchWheelValue% AND &H7F
msb% = (pitchWheelValue% \ 128) AND &H7F
msg& = lsb% + msb% * 256 + (MIDI_PITCHWHEEL OR channel%)
midiOutShortMsg& midiHandle&, msg&
END SUB
SUB PlayNoteWithPitchWheel (channel%, note%, velocity%, durationMS%, pitchWheelStart%, pitchWheelEnd%)
DIM startTime!, currentTime!, elapsedTime!
DIM pitchWheelValue%
DIM currentMS%
OpenMidi
SendMidiNote channel%, note%, velocity%
startTime! = TIMER
DO WHILE (TIMER - startTime!) * 1000 < durationMS%
currentTime! = TIMER
elapsedTime! = (currentTime! - startTime!) * 1000
' Calculate pitch wheel value (linear interpolation)
pitchWheelValue% = pitchWheelStart% + (pitchWheelEnd% - pitchWheelStart%) * (elapsedTime! / durationMS%)
SendPitchWheel channel%, pitchWheelValue%
'Delay to reduce cpu usage.
currentMS% = (TIMER-startTime!)*1000
DO WHILE ((TIMER-startTime!)*1000) < currentMS% + 1 : LOOP
LOOP
SendMidiNoteOff channel%, note%, velocity%
SendPitchWheel channel%, 8192 ' Reset pitch wheel to center
CloseMidi
END SUB
' Example Usage
OpenMidi
PlayNoteWithPitchWheel 0, 60, 100, 2000, 8192, 0 'Middle C, 2 sec, no bend to full bend down.
CloseMidi
END
Explanation and Key Differences (QB64PE):
- Library Declarations:
- QB64PE uses DECLARE LIBRARY "winmm.dll" to import the Windows Multimedia (WinMM) API functions.
- The function declarations are similar to VBA but use QB64PE's syntax.
- QB64PE uses DECLARE LIBRARY "winmm.dll" to import the Windows Multimedia (WinMM) API functions.
- Data Types:
- QB64PE uses % for integers, & for long integers, and ! for single-precision floating-point numbers.
- Correct data types are very important for the API calls to work.
- QB64PE uses % for integers, & for long integers, and ! for single-precision floating-point numbers.
- Constants:
- Constants are declared using CONST.
- Constants are declared using CONST.
- Subroutines:
- Subroutines are defined using SUB and END SUB.
- Subroutines are defined using SUB and END SUB.
- MIDI Message Construction:
- The MIDI message construction is the same as in VBA.
- The MIDI message construction is the same as in VBA.
- Pitch Wheel Handling:
- The pitch wheel calculation and handling are identical.
- The pitch wheel calculation and handling are identical.
- Timing:
- QB64PE uses the TIMER function for timing.
- A small delay loop is added within the PlayNoteWithPitchWheel subroutine to reduce CPU usage. This is essential, because without it, the loop will consume 100% of a CPU core.
- QB64PE uses the TIMER function for timing.
- Example Usage:
- The example usage is included directly in the main program.
- The example usage is included directly in the main program.
- No DoEvents:
- QB64PE does not have a direct equivalent of VBA's DoEvents. The delay loop is a basic way of providing time for other processes.
- QB64PE does not have a direct equivalent of VBA's DoEvents. The delay loop is a basic way of providing time for other processes.
- Open QB64PE: Launch QB64PE.
- Paste Code: Paste the code into the QB64PE editor.
- Run Program: Press F5 to run the program.
- MIDI Setup: Ensure your MIDI keyboard is connected and working.
- Adjust Parameters: Modify the parameters in the PlayNoteWithPitchWheel call to experiment with different notes, velocities, durations, and pitch bend amounts.
- MIDI Drivers: Make sure your MIDI keyboard's drivers are correctly installed.
- Error Handling: Add more robust error handling as needed.
- Timing: The timing using TIMER and the delay loop might not be perfectly precise.
- CPU Usage: The delay loop is important to prevent excessive CPU usage.
- 64-bit: QB64PE is 64 bit, so the long integers are correct.
Read MIDI notes + pitch wheel from keyboard
Prompt: Write code in QB64PE 4.0.0 running on Windows 11 to read MIDI notes and the pitch wheel from a real MIDI keyboard plugged into the PC.
Code snippet
Code: (Select All)
DECLARE LIBRARY "winmm.dll"
FUNCTION midiInOpen& (lphMidiIn&, uDeviceID&, dwCallback&, dwCallbackInstance&, dwFlags&)
FUNCTION midiInClose& (hMidiIn&)
FUNCTION midiInStart& (hMidiIn&)
FUNCTION midiInStop& (hMidiIn&)
FUNCTION midiInReset& (hMidiIn&)
END DECLARE LIBRARY
CONST MIDI_MAPPER = -1
CONST MIDI_NOTEON = &H90
CONST MIDI_NOTEOFF = &H80
CONST MIDI_PITCHWHEEL = &HE0
CONST MM_MIM_DATA = &H3C1
CONST CALLBACK_FUNCTION = 1
DIM midiInHandle&
DIM midiCallbackAddress&
TYPE MIDIHDR
lpData&
dwBufferLength&
dwBytesRecorded&
dwUser&
dwFlags&
lpNext&
dwReserved&
dwOffset&
dwReserved2&(3)
END TYPE
DIM midiBuffer%(0 TO 0) ' Single byte buffer
DIM midiHeader AS MIDIHDR
SUB OpenMidiInput
DIM result&
midiCallbackAddress& = VARSEG(MidiInProc) * &H10000 + VARPTR(MidiInProc) ' Callback address
result& = midiInOpen&(midiInHandle&, MIDI_MAPPER, midiCallbackAddress&, 0, CALLBACK_FUNCTION)
IF result& <> 0 THEN
PRINT "Could not open MIDI input device."
END
END IF
result& = midiInStart&(midiInHandle&)
IF result& <> 0 THEN
PRINT "Could not start MIDI input."
midiInClose& midiInHandle&
midiInHandle& = 0
END
END IF
midiHeader.lpData& = VARPTR(midiBuffer%(0))
midiHeader.dwBufferLength& = 1
' Add midiInAddBuffer here if needed.
END SUB
SUB CloseMidiInput
IF midiInHandle& <> 0 THEN
midiInStop& midiInHandle&
midiInReset& midiInHandle&
midiInClose& midiInHandle&
midiInHandle& = 0
END IF
END SUB
SUB MidiInProc (hMidiIn&, wMsg&, dwInstance&, dwParam1&, dwParam2&)
DIM status&
DIM data1%
DIM data2%
DIM channel%
DIM note%
DIM velocity%
DIM pitchWheelLSB%
DIM pitchWheelMSB%
DIM pitchWheelValue%
IF wMsg& = MM_MIM_DATA THEN
status& = dwParam1& AND &HF0
channel% = dwParam1& AND &HF
data1% = (dwParam1& \ &H100) AND &H7F
data2% = (dwParam1& \ &H10000) AND &H7F
SELECT CASE status&
CASE MIDI_NOTEON
note% = data1%
velocity% = data2%
PRINT "Note On: Channel="; channel% + 1; ", Note="; note%; ", Velocity="; velocity%
CASE MIDI_NOTEOFF
note% = data1%
velocity% = data2%
PRINT "Note Off: Channel="; channel% + 1; ", Note="; note%; ", Velocity="; velocity%
CASE MIDI_PITCHWHEEL
pitchWheelLSB% = data1%
pitchWheelMSB% = data2%
pitchWheelValue% = pitchWheelLSB% + (pitchWheelMSB% * 128)
PRINT "Pitch Wheel: Channel="; channel% + 1; ", Value="; pitchWheelValue%
CASE ELSE
PRINT "MIDI Message: Status="; HEX$(status&); ", Data1="; data1%; ", Data2="; data2%
END SELECT
END IF
END SUB
' Example Usage
OpenMidiInput
PRINT "Listening for MIDI input. Press any key to stop."
DO
IF INKEY$ <> "" THEN EXIT DO
LOOP
CloseMidiInput
END
Key Changes and Explanations for QB64PE:
- Library Declarations:
- DECLARE LIBRARY "winmm.dll" is used for WinMM API functions.
- DECLARE LIBRARY "winmm.dll" is used for WinMM API functions.
- Data Types:
- Correct data types are crucial (% for integers, & for long integers).
- Correct data types are crucial (% for integers, & for long integers).
- Callback Address:
- midiCallbackAddress& = VARSEG(MidiInProc) * &H10000 + VARPTR(MidiInProc) is used to calculate the callback address. This is essential in QB64PE.
- midiCallbackAddress& = VARSEG(MidiInProc) * &H10000 + VARPTR(MidiInProc) is used to calculate the callback address. This is essential in QB64PE.
- MIDI Header:
- The MIDIHDR type and midiBuffer% are used for receiving MIDI data.
- The MIDIHDR type and midiBuffer% are used for receiving MIDI data.
- Opening and Closing MIDI Input:
- OpenMidiInput and CloseMidiInput handle device opening and closing.
- OpenMidiInput and CloseMidiInput handle device opening and closing.
- Callback Function (MidiInProc):
- This function receives and parses MIDI messages.
- The SELECT CASE statement handles MIDI_NOTEON, MIDI_NOTEOFF, and MIDI_PITCHWHEEL.
- This function receives and parses MIDI messages.
- Message Parsing:
- The code extracts the status byte, channel, and data bytes from dwParam1&.
- The pitch wheel value is calculated.
- The code extracts the status byte, channel, and data bytes from dwParam1&.
- Example Usage:
- The example starts MIDI input and waits for a key press to stop.
- MIDI messages are printed to the console.
- The example starts MIDI input and waits for a key press to stop.
- Console Output:
- PRINT statements display MIDI data in the console.
- PRINT statements display MIDI data in the console.
- No DoEvents:
- In a simple listening loop, a tight loop is acceptable. If you need it to run in the background while the user does other things, you will need to restructure the code.
- In a simple listening loop, a tight loop is acceptable. If you need it to run in the background while the user does other things, you will need to restructure the code.
- Open QB64PE: Launch QB64PE.
- Paste Code: Paste the code into the editor.
- Run Program: Press F5.
- Play Keyboard: Play your MIDI keyboard. The MIDI messages will be displayed in the console.
- Stop: Press any key to stop the program.
- MIDI Setup: Ensure your MIDI keyboard is connected and working.