01-03-2025, 12:16 PM
That's correct. MIDI support is out of $UNSTABLE and hence $UNSTABLE:MIDI and $MIDISOUNDFONT are not supported anymore.
We have a new command for specifying MIDI sound fonts/banks - _MIDISOUNDBANK. We also have a good discussion on the same here: https://qb64phoenix.com/forum/showthread.php?tid=2979
I made a tiny change to Dav's MidiNotes.bas v1.08 to make it work on QB64-PE v4.0.0 and older versions.
@eoredson BTW, your forum PM is disabled.
We have a new command for specifying MIDI sound fonts/banks - _MIDISOUNDBANK. We also have a good discussion on the same here: https://qb64phoenix.com/forum/showthread.php?tid=2979
I made a tiny change to Dav's MidiNotes.bas v1.08 to make it work on QB64-PE v4.0.0 and older versions.
Code: (Select All)
'=============
'MidiNotes.bas - v1.08
'=============
'Generates MIDI file data from string of notes.
'Coded by Dav, SEP/2024
'Fixed: Corrected bad bug when playing groups of notes.
' Notes were not being given the proper "note off"
$IF VERSION < 4.0.0 THEN
$UNSTABLE: MIDI
$MIDISOUNDFONT: DEFAULT
$END IF
'THIS DEMO MAKES AND PLAYS 3 MIDI SONGS AS EXAMPLES.
'First MIDI Example: make silent night song in midi notes
a$ = "dq(g4e4c4) en(a4f4) qn(g4e4) dh(e4c4g3)" 'silent night
a$ = a$ + "dq(g4e4c4) en(a4f4) qn(g4e4) dh(e4c4g3)" 'holy night
a$ = a$ + "hn(d5b4) qn(d5b4) dq(g4b4) enc5 qn(d5b4)" 'all is calm
a$ = a$ + "hn(c5g4e4) qn(c5g4e4) dq(c4e4g4) end4e4g4" 'all is bright
a$ = a$ + "hn(a4f4c4) qn(a4f4c4) dq(c5a4f4) en(b4g4) qn(a4f4)" 'round yon virgin
a$ = a$ + "dq(g4e4c4) en(a4f4) qn(g4e4) dq(e4c4g3) enf4 qng4" 'mother and child
a$ = a$ + "dq(a4f4c4) enf4(a4f4)b4 qn(c5a4f4)(b4g4)(a4f4)" 'holy infant so
a$ = a$ + "dq(g4e4c4) en(a4f4) qn(g4e4) hn(e4c4g3) sng4a4(b4g4)(a4c5)" 'tender and mild
a$ = a$ + "hn(d5b4g4)qn(d5b4g4)dq(f5d5b4)en(d5b4)qn(b4g4f4)dh(c5g4e4)(e5c5g4)" 'sleep in heavenly peace
a$ = a$ + "qn(c5g4e4) g4 e4 dq(g4d4b4) enf4 qn(d4b3f4) wn(c4g3e4c3)" 'sleep in heavenly peace
'generate the midi data
note$ = MidiNotes$(100, 11, a$, 1)
'===================================================================
'optional here.... you could save that as a silentnight.mid file
'Open "silentnight.mid" For Output As #1: Print #1, note$;: Close #1
'===================================================================
'Open the MIDI using _SNDPLAY & "memory"
midisong& = _SNDOPEN(note$, "memory")
_SNDPLAY midisong&
'play the song
PRINT
PRINT "Playing Silent Night MIDI song..."
PRINT "Press any key to move on...."
SLEEP 41 'time to play it all
_SNDSTOP midisong&
_SNDCLOSE midisong&
'SECOND MIDI EXAMPLE - HOW TO MAKE A MUILTITRACK MIDI
'===================================================================
'BELOW SHOWS HOW TO MAKE A MILTI-TRACK MIDI SONG USING THE FUNCTION.
'===================================================================
'notes for all four instruments/tracks below...
p1$ = "p041sne4f4g4a4eng4qn(c5g4e4)en(d5g4)qn(c5e5)p080sne4f4g4a4eng4qn(c5e4)en(d5f4)qn(c5e4)" 'melody part (violin)
p2$ = "p011 wn (c4e4g4) p088 (c4e4g4)" 'harmony part (bells)
p3$ = "en c3g3(c3g2)g3c3g3(c3g2)g3c3g3(c3g2)g3c3g3c3g3" 'piano sound
p4$ = "c4eng3a3qnc4g3c4eng3a3qnc4g3" 'for the drum sounds
'let's make the song longer by doubling the song parts.
p1$ = p1$ + p1$: p2$ = p2$ + p2$
p3$ = p3$ + p3$: p4$ = p4$ + p4$
'generate midi data for 4 tracks.
'each track uses its own channel, 1 - 4.
'We only need MIDI header for the first track, so we are
'going to remove the MIDI header for track 2, 3 and 4 below
track1$ = MidiNotes$(100, 41, p1$, 1) 'keep first track as is, with a midi header
track2$ = MID$(MidiNotes$(100, 11, p2$, 2), 15) 'strip midi header off (removes 14 bytes)
track3$ = MID$(MidiNotes$(100, 1, p3$, 3), 15) 'strip
track4$ = MID$(MidiNotes$(100, 115, p4$, 10), 15) 'strip <<< use channel 10 for drums.
'combine tracks 1+2+3+4 to form one MIDI data file
note$ = track1$ + track2$ + track3$ + track4$
'**** IMPORTANT STEP BELOW WHEN MAKING MULTI-TRACK MIDIS THIS WAY ****
'last step, update midi header to say 4 tracks used, not 1 <<<<< important for multitracks
MID$(note$, 12, 1) = CHR$(4) '4 tracks now <<< your number of tracks used
'================================================================
'optional here.... you could save that as a MIDI file like so....
'Open "4tracks.mid" For Output As #1: Print #1, note$;: Close #1
'================================================================
'Open & play the 4 track midi..
midisong& = _SNDOPEN(note$, "memory")
_SNDPLAY midisong&
PRINT
PRINT "Playing a Multi-track MIDI song..."
PRINT "Press any key to move on..."
SLEEP 10
_SNDSTOP midisong&
_SNDCLOSE midisong&
'=================================================
'3rd MIDI example, lust play a simple midi song...
PRINT
PRINT "Playing a simple track song..."
PRINT
end$ = MidiNotes$(200, 5, "c5rneng4F4g4G4rnrng4rnrnrnrnrnenp016(b4f4)rnrnen(c5e4)qnrn", 1)
midisong2& = _SNDOPEN(end$, "memory")
_SNDPLAY midisong2&
SLEEP 4
_SNDSTOP midisong&
_SNDCLOSE midisong&
END
FUNCTION MidiNotes$ (tempo&, patch, notes$, channel)
'--------------------------------------------
'Returns MIDI data suitable for MIDI playback
'--------------------------------------------
'tempo& = tempo of the midi
'patch = program number (sound) to use (0-127)
'note$ = string data of notes to play
'channel = channel for track to use (1 to 16)
'You can play notes like this: "c4e4g4"
'To play notes at the same time you can
'put them in parenthesis: "(c4e4g4)"
'
'You can put duration codes in front of notes.
'Like this play eighth notes: "en c4e4g4"
'
'The following duration strings are allowed:
'
' wn = whole note
' dh = dotted half note
' hn = half note
' dq = dotted quarter
' qn = quarter note
' de = dotted eighth
' en = eighth note
' ds = dotted sixteenth note
' sn = sixteenth note
' ts = 32nd note
'
' rn - rest note - no sound
' (uses the set duration)
'
'You can change volume of notes by using
'values v000 to v127, like "v030c4"
'
'You can switch program sound # track uses.
'Use values p000 to p127, like "p011"
'--------------------------------------------
vol = 127 'default volume
'check and fix channel number here
'(for programming purpose channels are used 0-15)
channel = FIX(channel) - 1
IF channel < 0 THEN channel = 0
IF channel > 15 THEN channel = 15
'first, remove any spaces from the string
n2$ = ""
FOR i = 1 TO LEN(notes$)
a$ = MID$(notes$, i, 1): IF a$ <> " " THEN n2$ = n2$ + a$
NEXT
notes$ = n2$
'=======================================
'make MIDI Header Chunk (MThd)
MThd$ = "MThd"
MThd$ = MThd$ + CHR$(0) + CHR$(0) + CHR$(0) + CHR$(6) 'header size
MThd$ = MThd$ + CHR$(0) + CHR$(1) 'format type (1)
MThd$ = MThd$ + CHR$(0) + CHR$(1) 'number of tracks (1)
MThd$ = MThd$ + CHR$(1) + CHR$(224) 'division (ticks per quarter note)
'=======================================
'make tempo data to save
'calculate microseconds per beat from tempo& in BPM
MicroSecsPerBeat& = 60000000 \ tempo& 'Converts BPM to microseconds per beat
'get msb/mb/lsb from MicroSecsPerBeat& for saving tempo
'(Midi requires 3 bytes for this info)
msb = (MicroSecsPerBeat& \ 65536) AND 255 'most significant byte
middle = (MicroSecsPerBeat& \ 256) AND 255 'middle byte
lsb = MicroSecsPerBeat& AND 255 'least significant byte
'make the tempo data + the 3 bytes
TrackData$ = CHR$(0) + CHR$(255) + CHR$(81) + CHR$(3) + CHR$(msb) + CHR$(middle) + CHR$(lsb)
'======================================
'set Program number (patch) to use
TrackData$ = TrackData$ + CHR$(0) + CHR$(192 + channel) + CHR$(patch)
'======================================
'define ticks
'Set defaut note duration, get tickdata$
ticks& = 480 'default 480 ticks per quarter note
GOSUB GetTickData
'=====================================================
'Load all notes string
'Loop through all notes$ given
FOR n = 1 TO LEN(notes$) STEP 2
'see if it's a group first ()
IF MID$(notes$, n, 1) = "(" THEN
'grab group string of notes until a ) found
group$ = "": count = 0: n = n + 1
DO
g$ = MID$(notes$, n + count, 1)
IF g$ = ")" THEN EXIT DO ELSE group$ = group$ + g$: count = count + 1
LOOP
'========================
'do group notes here here
notesoff$ = "" ' holding space for turning notes off
FOR g = 1 TO LEN(group$) STEP 2
b2$ = MID$(group$, g, 2) 'grab 2 bytes
'must be a note, so get note val
GOSUB GetNoteValue
notesoff$ = notesoff$ + _TRIM$(STR$(note)) 'save this for use
'set Note On: play note value (with velocity 127)
TrackData$ = TrackData$ + CHR$(0) + CHR$(144 + channel) + CHR$(note) + CHR$(vol)
NEXT
addtick = 0 'only add tickdata once flag
'now send notes off to those above
FOR g = 1 TO LEN(notesoff$) STEP 2
note = VAL(MID$(notesoff$, g, 2))
'set note off: stop playing note after specified duration
IF addtick = 0 THEN
'only add tickdata first time around
TrackData$ = TrackData$ + TickData$ + CHR$(128 + channel) + CHR$(note) + CHR$(64)
addtick = 1 'mark we done it
ELSE
TrackData$ = TrackData$ + CHR$(0) + CHR$(128 + channel) + CHR$(note) + CHR$(64)
END IF
NEXT
n = (n + count - 1) 'update n location
_CONTINUE
END IF
'see if it's a volume change
IF LCASE$(MID$(notes$, n, 1)) = "v" THEN
vol = INT(VAL(MID$(notes$, n + 1, 3)))
IF vol < 0 THEN vol = 0
IF vol > 127 THEN vol = 127
n = n + 2: _CONTINUE
END IF
'see if it's a program change
IF LCASE$(MID$(notes$, n, 1)) = "p" THEN
pro = INT(VAL(MID$(notes$, n + 1, 3)))
IF pro < 0 THEN pro = 0
IF pro > 127 THEN pro = 127
'set Program number (patch) to use
TrackData$ = TrackData$ + CHR$(0) + CHR$(192 + channel) + CHR$(pro)
n = n + 2: _CONTINUE
END IF
'do regular bytes (not a group)
b2$ = MID$(notes$, n, 2) 'grab 2 bytes
'see if b2$ is a duration change
SELECT CASE LCASE$(b2$)
CASE "wn", "dh", "hn", "qn", "dq", "de", "en", "ds", "sn", "ts"
'all these values are based on 'ticks per quarter note' = 480
IF b2$ = "wn" THEN ticks& = 1920 'whole note (4 * tpq)
IF b2$ = "dh" THEN ticks& = 1440 'dotted half note (3 * tpq)
IF b2$ = "hn" THEN ticks& = 960 'half note (2 * tpq)
IF b2$ = "dq" THEN ticks& = 720 'dotted quarter (1.5 * tpq )
IF b2$ = "qn" THEN ticks& = 480 'quarter note
IF b2$ = "de" THEN ticks& = 360 'dotted eighth (.75 * tpq)
IF b2$ = "en" THEN ticks& = 240 'eighth note (.5 * tpq)
IF b2$ = "ds" THEN ticks& = 180 'dotted sixteenth note (sn + (.5 * sn)
IF b2$ = "sn" THEN ticks& = 120 'sixteenth note (.25 * tpq)
IF b2$ = "ts" THEN ticks& = 60 '32nd notes (1 / 8) * tpq
GOSUB GetTickData
_CONTINUE
END SELECT
'check for a rest note, handle special
IF LCASE$(MID$(notes$, n, 2)) = "rn" THEN
'(play any note at zero volume? (this just fakes a rest)
'TrackData$ = TrackData$ + Chr$(0) + Chr$(144 + channel) + Chr$(22) + Chr$(0)
'set rest note off: stop playing note after specified duration
TrackData$ = TrackData$ + TickData$ + CHR$(128 + channel) + CHR$(note) + CHR$(64)
_CONTINUE
END IF
'must be a note, so get note val
GOSUB GetNoteValue
'================================
'set Note On: play note value (with velocity 127)
TrackData$ = TrackData$ + CHR$(0) + CHR$(144 + channel) + CHR$(note) + CHR$(vol)
'===============================
'set note off: stop playing note after specified duration
TrackData$ = TrackData$ + TickData$ + CHR$(128 + channel) + CHR$(note) + CHR$(64)
'================================
NEXT
'============================
'SAVE TRACK DATA
'make the MTrk header
MTrk$ = "MTrk"
'make track end event
TrackData$ = TrackData$ + CHR$(0) + CHR$(255) + CHR$(47) + CHR$(0)
'make the track data length (4 bytes)
TrackLen& = LEN(TrackData$)
TrackLength$ = CHR$((TrackLen& \ 16777216) AND 255) + CHR$((TrackLen& \ 65536) AND 255) + CHR$((TrackLen& \ 256) AND 255) + CHR$(TrackLen& AND 255)
'============================
'put it all together
MidiNotes$ = MThd$ + MTrk$ + TrackLength$ + TrackData$
EXIT FUNCTION
'====================================================================================
'GOSUBS...Like'em or not, they're here to use.
' (keeping eveything in one FUNCTION)
'=============================================
GetTickData:
'==========
'convert ticks& to variable length quantity (VLQ)
TickData$ = ""
IF ticks& = 0 THEN
TickData$ = CHR$(0) 'safety, in case ticks& = 0
ELSE
DO
byte& = ticks& AND &H7F
ticks& = ticks& \ 128
IF TickData$ <> "" THEN byte& = byte& OR &H80
TickData$ = CHR$(byte&) + TickData$
LOOP WHILE ticks& <> 0
END IF
RETURN
'============================================
GetNoteValue:
'===========
'Gets a note value from b2$
note = 0 'safety defaut
IF b2$ = "a0" THEN note = 21
IF b2$ = "A0" THEN note = 22
IF b2$ = "b0" THEN note = 23
IF b2$ = "c1" THEN note = 24
IF b2$ = "C1" THEN note = 25
IF b2$ = "d1" THEN note = 26
IF b2$ = "D1" THEN note = 27
IF b2$ = "e1" THEN note = 28
IF b2$ = "f1" THEN note = 29
IF b2$ = "F1" THEN note = 30
IF b2$ = "g1" THEN note = 31
IF b2$ = "G1" THEN note = 32
IF b2$ = "a1" THEN note = 33
IF b2$ = "A1" THEN note = 34
IF b2$ = "b1" THEN note = 35
IF b2$ = "c2" THEN note = 36
IF b2$ = "C2" THEN note = 37
IF b2$ = "d2" THEN note = 38
IF b2$ = "D2" THEN note = 39
IF b2$ = "e2" THEN note = 40
IF b2$ = "f2" THEN note = 41
IF b2$ = "F2" THEN note = 42
IF b2$ = "g2" THEN note = 43
IF b2$ = "G2" THEN note = 44
IF b2$ = "a2" THEN note = 45
IF b2$ = "A2" THEN note = 46
IF b2$ = "b2" THEN note = 47
IF b2$ = "c3" THEN note = 48
IF b2$ = "C3" THEN note = 49
IF b2$ = "d3" THEN note = 50
IF b2$ = "D3" THEN note = 51
IF b2$ = "e3" THEN note = 52
IF b2$ = "f3" THEN note = 53
IF b2$ = "F3" THEN note = 54
IF b2$ = "g3" THEN note = 55
IF b2$ = "G3" THEN note = 56
IF b2$ = "a3" THEN note = 57
IF b2$ = "A3" THEN note = 58
IF b2$ = "b3" THEN note = 59
IF b2$ = "c4" THEN note = 60
IF b2$ = "C4" THEN note = 61
IF b2$ = "d4" THEN note = 62
IF b2$ = "D4" THEN note = 63
IF b2$ = "e4" THEN note = 64
IF b2$ = "f4" THEN note = 65
IF b2$ = "F4" THEN note = 66
IF b2$ = "g4" THEN note = 67
IF b2$ = "G4" THEN note = 68
IF b2$ = "a4" THEN note = 69
IF b2$ = "A4" THEN note = 70
IF b2$ = "b4" THEN note = 71
IF b2$ = "c5" THEN note = 72
IF b2$ = "C5" THEN note = 73
IF b2$ = "d5" THEN note = 74
IF b2$ = "D5" THEN note = 75
IF b2$ = "e5" THEN note = 76
IF b2$ = "f5" THEN note = 77
IF b2$ = "F5" THEN note = 78
IF b2$ = "g5" THEN note = 79
IF b2$ = "G5" THEN note = 80
IF b2$ = "a5" THEN note = 81
IF b2$ = "A5" THEN note = 82
IF b2$ = "b5" THEN note = 83
IF b2$ = "c6" THEN note = 84
IF b2$ = "C6" THEN note = 85
IF b2$ = "d6" THEN note = 86
IF b2$ = "D6" THEN note = 87
IF b2$ = "e6" THEN note = 88
IF b2$ = "f6" THEN note = 89
IF b2$ = "F6" THEN note = 90
IF b2$ = "g6" THEN note = 91
IF b2$ = "G6" THEN note = 92
IF b2$ = "a6" THEN note = 93
IF b2$ = "A6" THEN note = 94
IF b2$ = "b6" THEN note = 95
IF b2$ = "c7" THEN note = 96
IF b2$ = "C7" THEN note = 97
IF b2$ = "d7" THEN note = 98
IF b2$ = "D7" THEN note = 99
IF b2$ = "e7" THEN note = 100
IF b2$ = "f7" THEN note = 101
IF b2$ = "F7" THEN note = 102
IF b2$ = "g7" THEN note = 103
IF b2$ = "G7" THEN note = 104
IF b2$ = "a7" THEN note = 105
IF b2$ = "A7" THEN note = 106
IF b2$ = "b7" THEN note = 107
IF b2$ = "c8" THEN note = 108
RETURN
END FUNCTION
@eoredson BTW, your forum PM is disabled.