Exploring QB64-PE default soundfont patches - Dav - 09-10-2024
I was curious as to what all the default MIDI soundfont patches sounded like, and I also wanted to put together a way of grabbing those sounds somehow in code to use, so this function is the result. It just grabs 1 midi note. I don't think I'm calculating the ticks and duration right, but at least I can hear the sound patches available. There's some real neat ones. I'm probably not making a valid MIDI data file here, but at least it loads/plays.
- Dav
NOTE: Get the lastest version, now called MidiNotes, HERE
Code: (Select All)
'Exploring QB64-PE default soundfont patches.
'Makes MIDI a note and play's it from memory.
'Dav, SEP/2024
'Cycles throuh all default sound patchs 127-0
$Unstable:Midi
$MidiSoundFont: Default
'cycle through all sounds, press any key to quit.
For patch = 127 To 0 Step -1
Print "Patch#"; patch
note$ = MidiNote$(60, patch, 60, 1)
midisound& = _SndOpen(note$, "memory")
_SndPlay midisound&
_Delay 1
_SndStop midisound&
_SndClose midisound&
If InKey$ <> "" Then End
Next
Function MidiNote$ (tempo&, patch, note, duration&)
TicksPerQuarterNote = 96
'Make MIDI Header Chunk (MThd)
MThd$ = Chr$(77) + Chr$(84) + Chr$(104) + Chr$(100) ' "MThd"
'Make Header size
MThd$ = MThd$ + Chr$(0) + Chr$(0) + Chr$(0) + Chr$(6)
'Make format type (1 = single track)
MThd$ = MThd$ + Chr$(0) + Chr$(1)
'Make number of tracks (1)
MThd$ = MThd$ + Chr$(0) + Chr$(1)
'Make division = 96 (ticks per quarter note)
MThd$ = MThd$ + Chr$(0) + Chr$(96)
'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$ = 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) + Chr$(patch)
'Set Note On: play note value (with velocity 127)
TrackData$ = TrackData$ + Chr$(0) + Chr$(144) + Chr$(note) + Chr$(127)
'convert duration& in beats to ticks& (how long to play the note)
ticks& = duration& * TicksPerQuarterNote
'Set Note Off: Stop playing note after specified duration& in ticks&
TrackData$ = TrackData$ + Chr$(ticks&) + Chr$(128) + Chr$(note) + Chr$(64)
'Make track end event
TrackData$ = TrackData$ + Chr$(0) + Chr$(255) + Chr$(47) + Chr$(0)
'Make the MTrk header
MTrk$ = Chr$(77) + Chr$(84) + Chr$(114) + Chr$(107) ' MTrk
'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
MidiNote$ = MThd$ + MTrk$ + TrackLength$ + TrackData$
End Function
RE: Exploring QB64-PE default soundfont patches - a740g - 09-10-2024
Well, I was not expecting anyone to do this.
But this is brilliant. Good one @Dav.
RE: Exploring QB64-PE default soundfont patches - Dav - 09-10-2024
(09-10-2024, 06:42 AM)a740g Wrote: Well, I was not expecting anyone to do this.
But this is brilliant. Good one @Dav.
Thanks! I'm going to dig into this deeper, and learn more about the MIDI format. Looks like a MIDI making program is very possible in QB64-PE. Found a nice page of MIDI format info here.
I edited the code so it's not messed up. Looks like the QB tag needs a space between MidiSoundFont: and Default.
- Dav
RE: Exploring QB64-PE default soundfont patches - Dav - 09-10-2024
Here's a new version, it can now can play a string of given notes and you can specify the note duration in a musical way (whole note, quarter note, etc). I went ahead and named the program because I think it has some potential to expand on. I put the note duration stuff inside the notes FOR/NEXT loop because I intend to later allow duration calls in the notes string itself later on, like "qn606162hn656362", and not have it a separate parameter.
This version plays notes up and down on every sound patch. One more baby step.
- Dav
Code: (Select All)
'=============
'MidiNotes.bas
'=============
'Makes MIDI notes and plays it from memory.
'Exploring QB64-PE default soundfont patches.
'By Dav, SEP/2024 (v1.01)
$Unstable:Midi
$MidiSoundFont: Default
'cycle through all sounds, press any key to quit.
For patch = 0 To 127
Print "Patch#"; patch
note$ = MidiNotes$(120, patch, "6062646567656462", "en")
midisound& = _SndOpen(note$, "memory")
_SndPlay midisound&
_Delay 2
_SndStop midisound&
_SndClose midisound&
If InKey$ <> "" Then End
Next
Function MidiNotes$ (tempo&, patch, notes$, duration$)
'--------------------------------------------
'Returns MIDI data suitable for MIDI playback
'--------------------------------------------
'tempo& = tempo of the midi
'patch = program number (sound) to use
'note$ = string of note to play
'duration$ = the duration the note will have
'The following duration string is allowed:
' wn = whole note
' hn = half note
' dq = dotted quarter
' qn = quarter note
' de = dotted eighth
' en = eighth note
' sn = sixteenth note
TicksPerQuarterNote = 96 ' Set ticks per quarter note
'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$(0) + Chr$(TicksPerQuarterNote) 'division (ticks per quarter note)
'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$ = 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) + Chr$(patch)
'loop through all notes given
For n = 1 To Len(notes$) Step 2
note = Val(Mid$(notes$, n, 2)) 'get a note
'set Note On: play note value (with velocity 127)
TrackData$ = TrackData$ + Chr$(0) + Chr$(144) + Chr$(note) + Chr$(127)
'get notes duration in beats
Select Case LCase$(duration$)
Case "wn": ticks& = 4 * TicksPerQuarterNote 'whole note (4 beats)
Case "hn": ticks& = 2 * TicksPerQuarterNote 'half note (2 beats)
Case "qn": ticks& = 1 * TicksPerQuarterNote 'quarter note (1 beat)
Case "dq": ticks& = (3 / 2) * TicksPerQuarterNote 'dotted quarter (1.5 beats)
Case "en": ticks& = (1 / 2) * TicksPerQuarterNote 'eighth note (.5 beat)
Case "de": ticks& = (3 / 4) * TicksPerQuarterNote 'dotted eighth (.75 beat)
Case "sn": ticks& = (1 / 4) * TicksPerQuarterNote 'sixteenth note (.25 beat)
Case Else
ticks& = 0 'If invalid, set default to 0
End Select
'================================================
'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$ = TickData$ + Chr$(byte&)
Loop While ticks& <> 0
End If
'===============================================
'set note off: stop playing note after specified duration
TrackData$ = TrackData$ + TickData$ + Chr$(128) + Chr$(note) + Chr$(64)
Next
'make track end event
TrackData$ = TrackData$ + Chr$(0) + Chr$(255) + Chr$(47) + Chr$(0)
'make the MTrk header
MTrk$ = "MTrk"
'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$
End Function
RE: Exploring QB64-PE default soundfont patches - FellippeHeitor - 09-10-2024
That’s really cool! I wonder if PLAY could eventually evolve to take advantage of the new MIDI capabilities.
RE: Exploring QB64-PE default soundfont patches - Dav - 09-10-2024
That would be cool, @FellippeHeitor. And HEY!
Looks like all MIDI data opened with _SNDOPEN(filedata$, "memory") loop forever for me when played in any way. Is this on purpose? It doesn't do that when playing other audio file data from memory, like ogg's, Only MIDI.
- Dav
RE: Exploring QB64-PE default soundfont patches - a740g - 09-11-2024
(09-10-2024, 07:47 PM)Dav Wrote: Looks like all MIDI data opened with _SNDOPEN(filedata$, "memory") loop forever for me when played in any way. Is this on purpose? It doesn't do that when playing other audio file data from memory, like ogg's, Only MIDI. This does not happen to me in v3.14.1.
We changed the entire MIDI engine in v3.14. Which version are you using?
Update: I was able to recreate the bug in v3.13.1 and older. So, we fixed it!
RE: Exploring QB64-PE default soundfont patches - Dav - 09-11-2024
I’m still using in 3.13 on the laptop I’m using to make it. I will upgrade today. Glad to hear that it’s fixed! Now I can add some cool sound effects for my ballshoot game. Will post a new version of this program today probably. Fully implemented a PLAY-like system for making MIDI songs.
- Dav
RE: Exploring QB64-PE default soundfont patches - bplus - 09-11-2024
Wow nice selection of sounds like different instruments playing.
RE: Exploring QB64-PE default soundfont patches - Dav - 09-11-2024
Yeah, the sounds are a good resource that should be tapped. The resulting midi data is small and can be added to code fairly easy. I wanted to do this mainly to get some better sound effects that can be used in code.
Here's a laser or siren sound. Just an example of how it can be put/used in code..
- Dav
Code: (Select All)
$Unstable:Midi
$MidiSoundFont: Default
'make laser& sound
a$ = "4D546864000000060001000100604D54726B0000001600FF51030F424000C07B00903C7F60803C4000FF2F00"
laser$ = ""
For i = 1 To Len(a$) Step 2
laser$ = laser$ + Chr$(Val("&H" + Mid$(a$, i, 2)))
Next
laser& = _SndOpen(laser$, "memory")
_SndPlay laser&: _Delay 1: _SndStop laser&
|