Thread Rating:
  • 1 Vote(s) - 5 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Exploring QB64-PE default soundfont patches
#31
Great to hear!  I've only tested in Linux so far.   I need to add error checking stuff now.


I thought I'd give a brief overview on how to write MIDI songs using this program...

You must give a tempo of the song, a program name to use for sound (0-127), and your string$ data of notes to play.  Here's how to write note strings...

If you were sitting in front of a piano, played middle c, in this program that would be a "c4" (4 being the octave on the piano).  So if you played a scale from middle c to the next higher c, here it would look like this "c4d4e4g4a4b4c5"  (that last c started a new octave).

Notes are case sensitive here.  Capital letters in notes make them a sharp note.   "c4" is a regular c, but C4 will be a C# on the piano.  For example, "d4F4a4"  Would be a D,F#,A on the piano, in the 4th octave.

You can play more than one note at the same time (in groups) by putting them in parenthesis, like "(c4e4g4)" will play a C chord of those 3 notes.
 
How long a note plays depends on the duration you specify.  By default notes will be a quarter note long (qn). But you can change that by putting a new duration code in front of the note.  For example, if you wanted to play the c scale in eighth notes, uses the "en" code in front of them, like "en c4d4e4f4g4a4b4c5".  When a duration code is given all notes afterwards will play that duration until you change it.  Duration codes cannot be inside of a group () of notes.  Place it in front of the group if needed "hn(c4e4g4)".  You can change the note duration as much as needed.

Here are the following note duration codes:  

wn = whole note
dh = dotted half note
hn = half note
dq = dotted quarter
qn = quarter note
de = dotted eighth note
en = eighth note
ds = dotted sixteenth not
sn = sixteenth not
ts = thirty second note

Here's a part of twinkle twinkle little star:  "qn c4c4 g4g4 a4a4    hn g4"
The first notes are using quarter notes (qn), all of them  (twinkle, twinkle, little).., 
The last note is a half note (hn) on the note g4 ( star...).

That's the basics to building  a string of notes here.  Spaces are ignored in the notes string.  So you can use spaces for visual reference if needed.  There's no error checking yet, so if you make something and it fails to play, there's probably a typo in the notes data somewhere..

- Dav

Find my programs here in Dav's QB64 Corner
Reply
#32
Dav, thanks for the valuable study material in this thread.  

I don't quite understand it, so I'll ask. When you write the notes there, does each one have a fixed volume, or is it determined by the type of the note, or can it be adjusted separately for each note?

This is another big step forward with music in QB64.


Reply
#33
Thanks, Petr.  I had the volume hard-coded as full volume 127 when writing the notes, chr$(127).  It would be nice to control that.  Here's a new version that let's you change the volume anytime for any note.  You can now do a v000  to a v127 command in the note string.  like "v030 c4e4g4e4 v127 c4e4g4e4".  Thanks for the suggestion.

- Dav

Code: (Select All)
'=============
'MidiNotes.bas - v1.05
'=============
'Generates MIDI file data from string of notes.
'Coded by Dav, SEP/2024

'Added: Can change volume using code v000 to v127

$Unstable:Midi
$MidiSoundFont: Default

'Example: make silent night song 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 midi data
note$ = MidiNotes$(100, 11, a$)

'=================================================================
'optional here.... you could save that as a silentnight.mid file
'Open "silentnight.mid" For Output As #1: Print #1, note$;: Close #1
'=================================================================

midisong& = _SndOpen(note$, "memory")
_SndPlay midisong&

Print "Playing Silent Night MIDI song..."
Print "Press any key to stop."
Sleep

_SndStop midisong&
_SndClose midisong&

End


Function MidiNotes$ (tempo&, patch, notes$)
    '--------------------------------------------
    '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
    '
    '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
    '
    'You can change volume of notes by using
    'values v000 to v127, like "v030c4"
    '--------------------------------------------

    vol = 127 'default volume

    '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) + 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$ + Str$(note) 'save this for use
                'set Note On: play note value (with velocity 127)
                TrackData$ = TrackData$ + Chr$(0) + Chr$(144) + 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) + Chr$(note) + Chr$(64)
                    addtick = 1 'mark we done it
                Else
                    TrackData$ = TrackData$ + Chr$(0) + Chr$(128) + 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
        '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
        '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) + Chr$(note) + Chr$(vol)
        '===============================
        'set note off: stop playing note after specified duration
        TrackData$ = TrackData$ + TickData$ + Chr$(128) + 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

Find my programs here in Dav's QB64 Corner
Reply
#34
@Dav, version 1.04 works perfectly. Nice work! Might I add that it sounds very nice with the default FM synth.
Reply
#35
(09-12-2024, 10:18 AM)a740g Wrote: @Dav, version 1.04 works perfectly. Nice work! Might I add that it sounds very nice with the default FM synth.

Great!  Thanks.  I’m exploring the idea of making multi-track midis, to have more than one instrument playing. Will need to read up on that.

By the way, in v1.05 just posted with the volume control, you don’t have to set a volume, the song will default to full volume,  it just  can be changed anytime now using the v000 values (must always be 4 characters in length).

- Dav

Find my programs here in Dav's QB64 Corner
Reply
#36
Well, this actually works for combining two tracks playing at the same time, but it doesn't use 2 different sounds yet, and there seems to be some note cancellation going one, but the tracks are playing together.  I imagine I need to look into midi channels and program change use in a midi track.  Each track needs their own I'm thinking. 

- Dav

Code: (Select All)
'generate midi data for 2 tracks
track1$ = MidiNotes$(100, 15, "enc4c4c4c4c4c4c4c4") 'keep first track as is, with a midi header
track2$ = Mid$(MidiNotes$(100, 11, "g5a5g5e5"), 15) 'strip this midi header off (removes 14 bytes)
'combine tracks 1 & 2
note$ = track1$ + track2$
'update midi header byte #12 to say 2 tracks now, not 1
Mid$(note$, 12, 1) = Chr$(2) 'two tracks now
midisong& = _SndOpen(note$, "memory")
_SndPlay midisong&

EDIT: From what I'm reading, the midi channel for each track needs to be given for each note call.  So I made a parameter to assign a midi channel when creating the MidiNotes$.  This eliminated the note cancellations and the tracks play ok.  But can't assign different sounds to the tracks yet.

Find my programs here in Dav's QB64 Corner
Reply
#37
I am wondering if you can play rounds like Row, row, row your boat... as per Penny and Sheldon on Big Bang.
b = b + ...
Reply
#38
(09-12-2024, 03:21 PM)bplus Wrote: I am wondering if you can play rounds like Row, row, row your boat... as per Penny and Sheldon on Big Bang.

Try this.   Tongue Big Grin


.mid   row-row-row-your-boat-round.mid (Size: 1.01 KB / Downloads: 23)
Reply
#39
(@bplus, I dunno, never watched that show before...)

New version here, which really makes the program a lot more fun to use.  Added multi-track capability which means MIDI songs can now have more than one instrument playing at the same time.  You have to assign a channel for each track, and update the MIDI header to reflect the number of tracks (shows how in code).

This new one makes and plays a 4 track demo MIDI with 4 different instruments playing 4 different parts.  You can still use the MidiNotes$ as before to make a simple one track song, but I put in te multi-track creations to show how that works.

Please test and see if this works for you, it's working for me here.

- Dav

Code: (Select All)
'=============
'MidiNotes.bas - v1.06
'=============
'Generates MIDI file data from string of notes.
'Coded by Dav, SEP/2024

'Added: Now has channel parameter so you can assign
'      what MIDI channel a track will use (1 to 16).
'      This means you can make a multi-track MIDI and
'      each track can have its own notes and instrument.

$Unstable:Midi
$MidiSoundFont: Default

'======================================================
'THIS DEMO SHOWS HOW TO MAKE A MILTI-TRACK MIDI SONG.
'YOU DON'T HAVE TO DO THIS, YOU CAN MAKE A 1 TRACK TOO:
'Like,  song$ = MidiNotes$(120, 1, "c4e4g4e4f4d4", 1)
'Then just play single track that using _SNDPLAY.
'======================================================

'4-track song demo....

'notes for all four instruments below...
p1$ = "v100sne4f4g4a4eng4qnc5end5qne5sne4f4g4a4eng4qnc5end5qnc5" 'melody part (violin)
p2$ = "wn(c4e4g4)(c4e4g4)" 'harmony part (bells)
p3$ = "enc3g3(c3g2)g3c3g3(c3g2)g3c3g3(c3g2)g3c3g3c3g3" 'piano sound
p4$ = "c4eng3a3qnc4g3c4eng3a3qnc4g3" 'a drum sound
'let's make the song a longer one 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$, 4), 15) 'strip

'combine tracks 1+2+3+4 to form one MIDI data file
note$ = track1$ + track2$ + track3$ + track4$

'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
'================================================================

'play the song...
midisong& = _SndOpen(note$, "memory")
_SndPlay midisong&

Print "Playing Multi-track MIDI song..."
Print "Press any key to stop."
Sleep

_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
    '
    'You can change volume of notes by using
    'values v000 to v127, like "v030c4"
    '--------------------------------------------

    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$ + 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
        '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
        '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

Find my programs here in Dav's QB64 Corner
Reply
#40
(09-12-2024, 05:54 PM)Steffan-68 Wrote:
(09-12-2024, 03:21 PM)bplus Wrote: I am wondering if you can play rounds like Row, row, row your boat... as per Penny and Sheldon on Big Bang.

Try this.   Tongue Big Grin

2 distict instruments might help tenor and bass, sorta hear an echo...

@Dav works for me sounds like New Age Celtic band Smile

For Row, Row... I just meant a 2nd voice joins in with Row, row while the first voice is doing Merrily, Merrily... need different instruments/voices to hear both going together in contrast.
b = b + ...
Reply




Users browsing this thread: 4 Guest(s)