QB64 Phoenix Edition
Exploring QB64-PE default soundfont patches - Printable Version

+- QB64 Phoenix Edition (https://qb64phoenix.com/forum)
+-- Forum: QB64 Rising (https://qb64phoenix.com/forum/forumdisplay.php?fid=1)
+--- Forum: Code and Stuff (https://qb64phoenix.com/forum/forumdisplay.php?fid=3)
+---- Forum: Programs (https://qb64phoenix.com/forum/forumdisplay.php?fid=7)
+---- Thread: Exploring QB64-PE default soundfont patches (/showthread.php?tid=3016)

Pages: 1 2 3 4 5 6


RE: Exploring QB64-PE default soundfont patches - Steffan-68 - 11-05-2024

(11-05-2024, 02:43 PM)Dav Wrote: I was planning to make a new instrument program, using midi notes instead of separate .ogg files for all the individual notes to play, but it looks like multiple MIDI files can't be playing at the same time cleanly like .ogg files can.  I'm getting distortion pretty bad when multiple MIDI files are being sounded.  Is there a way to prevent this?
Here's a small example just playing 8 separate notes (8 different MIDI files).  You need latest QB64PE.
- Dav
Code: (Select All)

'make all notes
Dim notes&(8)
notes&(1) = _SndOpen(MidiNotes$(100, 10, "c4rn", 1), "memory")
notes&(2) = _SndOpen(MidiNotes$(100, 10, "d4rn", 1), "memory")
notes&(3) = _SndOpen(MidiNotes$(100, 10, "e4rn", 1), "memory")
notes&(4) = _SndOpen(MidiNotes$(100, 10, "f4rn", 1), "memory")
notes&(5) = _SndOpen(MidiNotes$(100, 10, "g4rn", 1), "memory")
notes&(6) = _SndOpen(MidiNotes$(100, 10, "a4rn", 1), "memory")
notes&(7) = _SndOpen(MidiNotes$(100, 10, "b4rn", 1), "memory")
notes&(8) = _SndOpen(MidiNotes$(100, 10, "c5rn", 1), "memory")
'play notes
For t = 1 To 8
    _SndPlayCopy notes&(t)
    _Delay .2
Next
Things are going quite normally for me. With a note length and DELAY of .2, a reverb eleven corner occurs.
You can increase the delay but it doesn't sound as good.
Or
Code: (Select All)
notes&(1) = _SndOpen(MidiNotes$(100, 20, "tsc4rn", 1), "memory,NOASYNC") 

Shorten the note length.


RE: Exploring QB64-PE default soundfont patches - Dav - 11-05-2024

Thanks for the reply, Stefan-68.   I’m still getting crackling, like bad speakers.  I think it may be my laptop speakers that’s the problem.   They are really started to sound bad when playing YouTube vids.  Maybe they’re going out or something….

- Dav


RE: Exploring QB64-PE default soundfont patches - a740g - 11-06-2024

@Dav, I tried it, and it worked just fine for me.


RE: Exploring QB64-PE default soundfont patches - Dav - 11-06-2024

(11-06-2024, 12:31 AM)a740g Wrote: @Dav, I tried it, and it worked just fine for me.

Thanks for testing.  I just tested it on my Windows laptop and it sounds fine, but not on my Linux laptops.  They all crackle.  I'm leaning towards audio driver Linux uses for thinkpads - it's known to have issues.  I'll see what I can do here, looks like it's not a QB64pe problem.

- Dav


RE: Exploring QB64-PE default soundfont patches - grymmjack - 12-15-2024

(09-10-2024, 07:34 PM)FellippeHeitor Wrote: That’s really cool! I wonder if PLAY could eventually evolve to take advantage of the new MIDI capabilities.

That would be a great thing!

Imagine being able to change midi programs too Smile


RE: Exploring QB64-PE default soundfont patches - grymmjack - 12-15-2024

I ran this on Linux and it sounded great. No crackle. What audio drivers are you using? Alsa? If so, there might be buffer issue.

https://unix.stackexchange.com/questions/136583/why-would-alsa-sound-pop-crackle-and-be-generally-too-distorted-to-listen-to


RE: Exploring QB64-PE default soundfont patches - hsiangch_ong - 12-16-2024

i'm sorry about this reply if it looks imprudent.  i have tried the "midinotes" 1.08 with qb64pe 4.0 but sadly, it does not work.  when i go to compile it as it is it gives a fatal error in red, "use _MIDISOUNDBANK" because the compiler directive can no longer be used.  the programmer has to know the name of the sound bank.  alas the SF2 file did not come with the linux version of qb64pe 4.0.  i had to copy it to the respective destination from qb64pe 3.11 installation.

i used the following line in place of compiler directive:

Code: (Select All)
_MIDISOUNDBANK "default_soundfont.sf2"
should use compiler directive for qb64pe 4.0 to avoid the unstable thing which is not wanted anymore on that version.  otherwise program works in 3.14 and earlier.

@grymmjack

i hope that i am wrong at my end.  if you used qb64pe 4.0.  i am now on debian mate "bookworm", which hasn't been updated much but with "backports" kernel 6.10.6.

this function would be difficult to include in my program because i depend a lot on OPTION _EXPLICIT.  in the function code i changed the variable called "note" to "mynote" and made sure it is integer.  i also made a few other changes in the attempt to simplify it.  nobody has to really try this but it works well:

Code: (Select All)
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)
    'notes$ = 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"
    '--------------------------------------------
    'modified by "mnrvovrfc" (me):
    static thenotes$, uu&, mynote%
    if thenotes$ = "" then
    thenotes$ = "a0A0b0c1C1d1D1e1f1F1g1G1a1A1b1c2C2d2D2e2f2F2g2G2" +_
                "a2A2b2c3C3d3D3e3f3F3g3G3a3A3b3c4C4d4D4e4f4F4g4G4" +_
                "a4A4b4c5C5d5D5e5f5F5g5G5a5A5b5c6C6d6D6e6f6F6g6G6" +_
                "a6A6b6c7C7d7D7e7f7F7g7G7a7A7b7c8"
    end if

    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$(mynote%)) 'save this for use
                'set Note On: play note value (with velocity 127)
                TrackData$ = TrackData$ + Chr$(0) + Chr$(144 + channel) + Chr$(mynote%) + 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
                mynote% = 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$(mynote%) + Chr$(64)
                    addtick = 1 'mark we done it
                Else
                    TrackData$ = TrackData$ + Chr$(0) + Chr$(128 + channel) + Chr$(mynote%) + 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$(mynote%) + 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$(mynote%) + Chr$(vol)
        '===============================
        'set note off: stop playing note after specified duration
        TrackData$ = TrackData$ + TickData$ + Chr$(128 + channel) + Chr$(mynote%) + 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$
    mynote% = 0 'safety default
    uu& = instr(thenotes$, b2$)
    if uu& > 0 then mynote% = (uu& \ 2) + 20
    'If b2$ = "a0" Then mynote% = 21
    'If b2$ = "A0" Then mynote% = 22
    'If b2$ = "b0" Then mynote% = 23
    'If b2$ = "c1" Then mynote% = 24
    'If b2$ = "C1" Then mynote% = 25
    'If b2$ = "d1" Then mynote% = 26
    'If b2$ = "D1" Then mynote% = 27
    'If b2$ = "e1" Then mynote% = 28
    'If b2$ = "f1" Then mynote% = 29
    'If b2$ = "F1" Then mynote% = 30
    'If b2$ = "g1" Then mynote% = 31
    'If b2$ = "G1" Then mynote% = 32
    'If b2$ = "a1" Then mynote% = 33
    'If b2$ = "A1" Then mynote% = 34
    'If b2$ = "b1" Then mynote% = 35
    'If b2$ = "c2" Then mynote% = 36
    'If b2$ = "C2" Then mynote% = 37
    'If b2$ = "d2" Then mynote% = 38
    'If b2$ = "D2" Then mynote% = 39
    'If b2$ = "e2" Then mynote% = 40
    'If b2$ = "f2" Then mynote% = 41
    'If b2$ = "F2" Then mynote% = 42
    'If b2$ = "g2" Then mynote% = 43
    'If b2$ = "G2" Then mynote% = 44
    'If b2$ = "a2" Then mynote% = 45
    'If b2$ = "A2" Then mynote% = 46
    'If b2$ = "b2" Then mynote% = 47
    'If b2$ = "c3" Then mynote% = 48
    'If b2$ = "C3" Then mynote% = 49
    'If b2$ = "d3" Then mynote% = 50
    'If b2$ = "D3" Then mynote% = 51
    'If b2$ = "e3" Then mynote% = 52
    'If b2$ = "f3" Then mynote% = 53
    'If b2$ = "F3" Then mynote% = 54
    'If b2$ = "g3" Then mynote% = 55
    'If b2$ = "G3" Then mynote% = 56
    'If b2$ = "a3" Then mynote% = 57
    'If b2$ = "A3" Then mynote% = 58
    'If b2$ = "b3" Then mynote% = 59
    'If b2$ = "c4" Then mynote% = 60
    'If b2$ = "C4" Then mynote% = 61
    'If b2$ = "d4" Then mynote% = 62
    'If b2$ = "D4" Then mynote% = 63
    'If b2$ = "e4" Then mynote% = 64
    'If b2$ = "f4" Then mynote% = 65
    'If b2$ = "F4" Then mynote% = 66
    'If b2$ = "g4" Then mynote% = 67
    'If b2$ = "G4" Then mynote% = 68
    'If b2$ = "a4" Then mynote% = 69
    'If b2$ = "A4" Then mynote% = 70
    'If b2$ = "b4" Then mynote% = 71
    'If b2$ = "c5" Then mynote% = 72
    'If b2$ = "C5" Then mynote% = 73
    'If b2$ = "d5" Then mynote% = 74
    'If b2$ = "D5" Then mynote% = 75
    'If b2$ = "e5" Then mynote% = 76
    'If b2$ = "f5" Then mynote% = 77
    'If b2$ = "F5" Then mynote% = 78
    'If b2$ = "g5" Then mynote% = 79
    'If b2$ = "G5" Then mynote% = 80
    'If b2$ = "a5" Then mynote% = 81
    'If b2$ = "A5" Then mynote% = 82
    'If b2$ = "b5" Then mynote% = 83
    'If b2$ = "c6" Then mynote% = 84
    'If b2$ = "C6" Then mynote% = 85
    'If b2$ = "d6" Then mynote% = 86
    'If b2$ = "D6" Then mynote% = 87
    'If b2$ = "e6" Then mynote% = 88
    'If b2$ = "f6" Then mynote% = 89
    'If b2$ = "F6" Then mynote% = 90
    'If b2$ = "g6" Then mynote% = 91
    'If b2$ = "G6" Then mynote% = 92
    'If b2$ = "a6" Then mynote% = 93
    'If b2$ = "A6" Then mynote% = 94
    'If b2$ = "b6" Then mynote% = 95
    'If b2$ = "c7" Then mynote% = 96
    'If b2$ = "C7" Then mynote% = 97
    'If b2$ = "d7" Then mynote% = 98
    'If b2$ = "D7" Then mynote% = 99
    'If b2$ = "e7" Then mynote% = 100
    'If b2$ = "f7" Then mynote% = 101
    'If b2$ = "F7" Then mynote% = 102
    'If b2$ = "g7" Then mynote% = 103
    'If b2$ = "G7" Then mynote% = 104
    'If b2$ = "a7" Then mynote% = 105
    'If b2$ = "A7" Then mynote% = 106
    'If b2$ = "b7" Then mynote% = 107
    'If b2$ = "c8" Then mynote% = 108
    Return
End Function