MIDI Output: Some Things to Consider

© 2003, MIDI_boink

author email:

NoteJam@aol.com

Home

Password Textbox API

Character Replacement

LB Isam Library

Beginning Games 2

Rubber Band Objects

WMLiberty Primer

LB Browser

Beginning Programming 5

INPUTTO Demo

Chase Button

Questionaire Wizard

MIDI Output Thoughts

MIDI-Tunes

Play MIDI DLL

Directory Search Function

Newsletter help

Index

Introduction

As people continue to push the envelope of Liberty Basic we continue to see new and exciting things. One of these is the movement toward being able to actually sequence MIDI music using the windows Multimedia API. Early in June a member of the community who is active in sound processing and MIDI and goes by the alias "Midi_boink" was encouraged to write a newsletter article about this area. He declined, believing that his lack of experience with Liberty Basic made him ineligible to do such a thing. He however wrote a most enlightening post on the subject which he has graciously allowed me to reprint here. So, in Midi_boink's own words, here are some things to consider regarding MIDI file output using Liberty Basic:

I really am not ready to write for the newsletter as suggested. The reasons are many, first I am a beginner at programming in Liberty basic, and in addition, there are a lot of unsolved problems that need to be resolved before one can write such a program. First we need to resolve the timing of sending a stream, because that time will have its effects on a lot of other midi events, like tempo, and number of voices allowed at a time.

I at first thought we might have a timer for tic of clock, and then do an untimed list of midi events for that tic. My reason is that programs that do not play very many notes at a time can get away with this rough timing, and also, as soon as we put a time limit on how long we have to send a list of midi events, we put a limit on the number of voices we can play at the same time.

But if we want to make our program be able to handle soundcards that allow 576 voices at same time, like phillips rythemic edge, or 1024 voices like soundblaster live, then we are going to have to reconsider if we time our list of midi events.

Here is why: on one tic you might have no list to do, on the next tic, you might have 1024 voices to set, and each will take either a 3 byte or two byte code to set, so we have at least 1024 calls to our midi dll at times, and at others 0, much too jittery of a rough timing, and we would probably be quite off time for songs of 5 minutes or longer.

So if we time the output list for a tic, and then just wait until that time elapsed, even when we have no list of midi events to send, we will end up with accurate, exact timing, at least as good as liberty basic can give us with this command.

But the unknown, is how long should this time be? So we pick some time as a guess, and set it at that, and find out how many voices we can do at the same time on our machine, and realize that on slower machines they will not be able to do as long a list.

Now our second problem: We need to pick a time for the midi events processing that will allow us to also arrive at music tempos that can be set in even numbers, like 120, 121, and so forth, and range from about a tempo of 60 to a tempo of 240? So some more unknowns, what range of tempo do we want to allow?

Now the sum of the timer for tic, and the timer for the evnts, must be such that they allow even divisions of tempo. In other words we do not want to end up with tempos of 120.72 etc. So only a set of certain numbers for our time allowed to process the list is allowed. What is this list of magic numbers?

So we need someone that is good at math, to figure out and create a table of allowed numbers to set the list time at, and the clock tic timer at, to allow correct tempos to be set rather than odd fractional tempos.

a chart like this timer list for clock tics of 1 millisecond. On a 200 mhz pentium 2 milliseconds allows 32 voice polyphony tempos by steps of 1 3 milliseconds allows x voice polyphony but fractional tempos 4 64 voice polyphony tempos by steps of ?

After we know what numbers could be used, we could then pick from the ones that allowed us integer tempos, and a reasonble amount of polyphony for all computers the program is to run on. so another quetion, for a midi program that will run on all computers, what is the desired upper limit of polyphony? Would 32 voices be enough? or should we try for at least 64? So you see, we need some feed back from owners of old 286 and 386 computers, that have a slower speed. We need them to tell us what was the maximum number of polyphony they got on their computer? Then we would have at least a rough idea on what to set the time for programs that would run on any computer.

Of course, it can also be desireable to write midi programs that only ran on certain computers, instead of all, and that required a certain sound card and made use of the max allowed polyphony of those cards.

Now we also need to drop the send two bytes to the dll, or send 3 bytes to the dll method, and instead, just send a byte of the list to the dll, and then send next byte to the dll, and continue doing this, until we have sent the list of midi events for the tic of the clock.

The reason we want to change to this method for sending the list, is we do not have to use any time to parse the list of midi events to be sent, to figure out if they are two bytes or 3 byte code, and send them to the correct subroutine. Also we get rid of all the multiplications to pack the code, before it gets sent to the dll.

I think this will result in sending the list faster, and it at leasts lets us get out of having to parse the list.

Speaking of subroutines, we also need to find out if a subroutine is the best way to do this, or should we just have a for next loop, that gets the bytes of the list, then sends a byte at a time to the dll? Whats the faster method?

In other words we need to start optimizing our code now, so we can do as many midi events as possible.

So there are a lot of things we need to solve, and decide, before we can write a midi file output program and do it with the dll used by Alyce's piano.bas program.

There already is a program available, if you want your program to just play a midi file, and its in the liberty basic folder.

But I think your wanting to be able to write your own, and not make use of that dll, because then you could have greater control of the process, plus just learning to understand everything about midi programming.

Midi file headers can be in any order in the file, so do not count on track 1 header occuring before track 2.

Also you can add your own headers into a midi file, and that file will still play as a midi file. Your programs should just ignore any unknown headers they incounter in a file, and move on until they find a recognized header. This type of file structure has the advantage, that anyone can add an improvement to the file structure at any time, yet the file can still be played by programs that do not have the added feature. Karoke is an example of this. Most Karoke files, if you change the file extension to .mid, the song can be played on non karaoke midi players and sequencers as a midi file.

Some suggested new headers to create are: Animation frames Your midi file might also have animations embedded in it, and your program might at certain tics of the clock, advance to show the next frame of animation.

Plot at x. y draw to xx.yy This type of file might be one to draw a picture in time to the music playing. certain tics of the clock would trigger one or more graphic commands to occur, and the user would see some drawing slowly assemble in time to the music.

Karaoke files that allow more tracks than typical karaoke files. The karaoke files currently allow only a few tracks of music, can't you write a better file standard? One that allows all midi tracks?

Hope my rambling has helped,

Midi_boink

Some MIDI Demos

Midi_boink also has allowed me to publish several MIDI demos that he posted recently. There are three:

Windchimes 1

nomainwin

note = 0
voice = 35

dim note(26)
dim tune(100,2)

note(1) = 60 'C
note(2) = 61 'C#
note(3) = 62 'D
note(4) = 63 'D#
note(5) = 64 'E
note(6) = 65 'F
note(7) = 66 'F#
note(8) = 67 'G
note(9) = 68 'G#
note(10) = 69 'A
note(11) = 70 'A#
note(12) = 71 'B
note(13) = 72 'C
note(14) = 73 'C#
note(15) = 74 'D
note(16) = 75 'D#
note(18) = 76 'E
note(19) = 77 'F
note(20) = 78 'F#
note(21) = 79 'G
note(22) = 80 'G#
note(23) = 81 'A
note(24) = 82 'A#
note(25) = 81 'B
note(26) = 82 'C

tune(1,1) = 69
tune(2,1) = 69
tune(3,1) = 76
tune(4,1) = 76
tune(5,1) = 78
tune(6,1) = 78
tune(7,1) = 76

tune(8,1) = 74
tune(9,1) = 74
tune(10,1) = 73
tune(11,1) = 73
tune(12,1) = 71
tune(13,1) = 71
tune(14,1) = 69


tune(15,1) = 76
tune(16,1) = 76
tune(17,1) = 74
tune(18,1) = 74
tune(19,1) = 73
tune(20,1) = 73
tune(21,1) = 71

tune(22,1) = 76
tune(23,1) = 76
tune(24,1) = 74
tune(25,1) = 74
tune(26,1) = 73
tune(27,1) = 73
tune(28,1) = 71


tune(29,1) = 69
tune(30,1) = 69
tune(31,1) = 76
tune(32,1) = 76
tune(33,1) = 78
tune(34,1) = 78
tune(35,1) = 76

tune(36,1) = 74
tune(37,1) = 74
tune(38,1) = 73
tune(39,1) = 73
tune(40,1) = 71
tune(41,1) = 71
tune(42,1) = 69

for i = 1 to 42
 tune(i,2) = 200
 if i/14 = int(i/14) then tune(i,2) = 400
 if i/21 = int(i/21) then tune(i,2) = 400
 if i = 7 or i = 35 then tune(i,2) = 400
next i

struct m, a$ As ptr

calldll #winmm, "midiOutOpen",_
m as struct,_
-1 As long,_
0 as long,_
0 as long,_
0 as long,_
ret as long

hMidiOut = m.a$.struct

event = 192
velocity = 127
low = (voice * 256) + event
hi = velocity * 256 * 256
dwMsg = low + hi
calldll #winmm, "midiOutShortMsg",_
hMidiOut as ulong,_
dwMsg as ulong,_
ret as ulong

[timer]
timer tune(noteRef,2), [keyNote]
wait

[keyNote]
timer 0

event = 144
low = (note * 256) + event
hiZero = 0
dwMsg = low + hiZero
calldll #winmm, "midiOutShortMsg",_
hMidiOut as ulong,_
dwMsg as ulong,_
ret as ulong

noteRef = noteRef + 1
if noteRef = 43 then goto [quit]
note = tune(noteRef,1)

event = 144
low = (note * 256) + event
velocity = 127
hi = velocity * 256 * 256
dwMsg = low + hi
calldll #winmm, "midiOutShortMsg",_
hMidiOut as ulong,_
dwMsg as ulong,_
ret as ulong

goto [timer]


[quit]
timer 0
calldll #winmm, "midiOutClose", hMidiOut As ulong, ret As ulong
end


Windchimes 2

'This wind chime program is
'a modification of the
' example contributed
'by Liberty BASIC community
'member Alyce Watson!
'piano4.bas - a cool piano that uses
'Windows' built-in MIDI synthesizer
'plays one note at a time on channel 1 (channel 1 = 144)
'
'
'run this code from LB folder,
' piano bmp is in LB bmp folder
'if program will not run restart
'your computer and try again

NoMainWin
[initVariables]
note=0 'will contain value for note
voice=0 'voice 0
dim scale(16)
scale(1)=60 'C
scale(2)=62 'D
scale(3)=64 'E
scale(4)=65 'F
scale(5)=67 'G
scale(6)=69 'A
scale(7)=71 'B
scale(8)=72 'C
scale(9)=74 'D
scale(10)=76 'E
scale(11)=77 'F
scale(12)=79 'G
scale(13)=81 'A
scale(14)=83 'B
scale(15)=64 'C

Dim vo(22)
vo(1)=76
vo(2)=82
vo(3)=88
vo(4)=95
vo(5)=97
vo(6)=98
vo(7)=100
vo(8)=102
vo(9)=108
vo(10)=112
vo(11)=9
vo(12)=10
vo(13)=11
vo(14)=14
vo(15)=19
vo(16)=45
vo(17)=46
vo(18)=49
vo(19)=50
vo(20)=51
vo(21)=54
vo(22)=60


[windowSetup]
Graphicbox #p.g, 0,0,0,0
Open "Wind Chime #2" For Window_nf As #p
#p.g "setfocus"
#p.g "when characterInput [keyNote]"
#p "trapclose [quit]"

Wnd=hWnd(#p.g) 'handle of graphicbox
'get device context for graphicbox
CallDLL #user32, "GetDC",_
Wnd As long, hDC As long

'open midi device and obtain handle
'midi functions return 0 if successful

struct m, a$ As ptr
CallDLL #winmm, "midiOutOpen",_
m As struct,-1 As long,0 As long,_
0 As long,0 As long,ret As long

hMidiOut=m.a$.struct 'handle to midi device
voice=108
gosub[doChange]
[timer]
flip=int(rnd(1*8))

tt=(int(rnd(1)*4)+1)*100
if flip=5 then tt=450
timer tt,[keyNote]
Wait

[quit]'stop note, close midi device, DLLs, window
timer 0
CallDLL #winmm, "midiOutClose", hMidiOut As ulong,_
ret As ulong

CallDLL#user32,"ReleaseDC",_
Wnd As long,hDC As long,result As long
Close #p
End

[keyNote] 'a keyboard key was pressed
timer 0
gosub [stopNote]


c=c+1
if c=50 then c=0:gosub [doChange]
index=index+int(rnd(1)*7)-int(rnd(1)*7)
if index<1 then index=5
if index>15 then index=11


note=scale(index)
flip=int(rnd(1)*10)
if flip >=7 then note = note-1
if flip =2 then goto [timer]

gosub [playNewNote] 'play new note

goto [timer]
[rest] wait

[cutOff]'stop note played by typing on keyboard
gosub [stopNote]
wait

'GOSUBS:
[playNewNote]'play new note:

event=144 'event 144 = play on channel 1
low=(note*256)+event
velocity=127
hi=velocity*256*256
dwMsg=low+hi
CallDLL #winmm, "midiOutShortMsg",hMidiOut As ulong,_
dwMsg As ulong, ret As ulong
RETURN

[stopNote]'stop note from playing
timer 0
event=144 'event 144 = play on channel 1
low=(note*256)+event
hiZero=0 'stop note from sounding by setting velocity to 0
dwMsg=low+hiZero
CallDLL #winmm, "midiOutShortMsg",hMidiOut As ulong,_
dwMsg As ulong, ret As ulong

RETURN


[doChange]'signal a voice change:
event=192 'event 192 = change
'voice=int(rnd(1)*128)
v=int(rnd(1)*22)+1
voice=vo(v)
velocity=127
low=(voice*256)+event
hi=velocity*256*256
dwMsg=low+hi
CallDLL #winmm, "midiOutShortMsg",hMidiOut As ulong,_
dwMsg As ulong, ret As ulong
RETURN


Beethovan's Finger in a Box

'midi_boink - mailto:NoteJam@aol.com

'What if Beethoven only had the liberty basic program piano.bas to 
'compose with?  And what if he could only play it with one finger?
' 
'Run the following program,  Beethoven's Finger In A Box version 1 to 
'find out.   Sometimes it composes bad sounding music, but now and 
'then, it does a fairly good job.  Hey, even Beethoven had bad hair 
'days!


    dim scale(22)
    scale(1)=48 'C
    scale(2)=50 'D
    scale(3)=52 'E
    scale(4)=53 'F
    scale(5)=55 'G
    scale(6)=57 'A
    scale(7)=59 'B

    scale(8)=60 'C
    scale(9)=62 'D
    scale(10)=64 'E
    scale(11)=65 'F
    scale(12)=67 'G
    scale(13)=69 'A
    scale(14)=71 'B
    scale(15)=72 'C
    scale(16)=74 'D
    scale(17)=76 'E
    scale(18)=77 'F
    scale(19)=79 'G
    scale(20)=81 'A
    scale(21)=83 'B
    scale(22)=84 'C

    dim ryt(10)
    ryt(1)=96 'quarter
    ryt(2)=96
    ryt(3)=48 'eigth note
    ryt(4)=24 'sixteenth note
    ryt(5)=144 'dotted quarter
    ryt(6)=72 'dotted eighth

    Dim vo(22)
    vo(1)=60
    vo(2)=82
    vo(3)=88
    vo(4)=95
    vo(5)=97
    vo(6)=98
    vo(7)=100
    vo(8)=102
    vo(9)=108
    vo(10)=112
    vo(11)=9
    vo(12)=10
    vo(13)=11
    vo(14)=14
    vo(15)=19
    vo(16)=45
    vo(17)=46
    vo(18)=49
    vo(19)=50
    vo(20)=51
    vo(21)=54



       dim song(100)
     dim rythem(100)
    'open midi device and obtain handle
    'midi functions return 0 if successful
    struct m, a$ As ptr
    CallDLL #winmm, "midiOutOpen",_
        m As struct,-1 As long,0 As long,_
        0 As long,0 As long,ret As long

    hMidiOut=m.a$.struct    'handle to midi device

[scratchPad]
    For x=1 to 100
    index=index+int(rnd(1)*5)-int(rnd(1)*5)
    flip=int(rnd(1)*8):if flip=1 then index=index+int(rnd(1)*3)-int(rnd(1)*3)
    if index>22 then index=22
    if index<1 then index=3

    note=scale(index)
    randnum=int(rnd(1)*15)+1
    if randnum=1 then note=note-1
    if randnum>=14 then note=1 'rest
    song(x)=note
    r=int(rnd(1)*6)+1
    ry=ryt(r)

    rythem(x)=ry

    next x


[start]
          gosub [doChange]
           timer 0
          cc=int(rnd(1)*4)
          high1=int(rnd(1)*70)+29:low1=high1-(int(rnd(1)*8)+15)
          high2=int(rnd(1)*70)+29:low2=high2-(int(rnd(1)*8)+15)
          high3=int(rnd(1)*70)+29:low3=high3-(int(rnd(1)*8)+15)
          high4=int(rnd(1)*70)+15:low4=high4-(int(rnd(1)*8)+5)
          high5=int(rnd(1)*70)+15:low5=high5-(int(rnd(1)*8)+5)
          high6=int(rnd(1)*70)+15:low6=high6-(int(rnd(1)*8)+5)


[first]
       cc=int(rnd(1)*3)
       if cc=1 goto [second]
       if cc=2 goto [third]
       high=high1:x=low1: gosub [playSong]
[second]       high=high1:x=low1:gosub[playSong]
[third]       high=high1:x=low1:gosub[playSong]

       high=high2:x=low2:gosub [playSong]
       high=high3:x=low3:gosub [playSong]
       cc=int(rnd(1)*3)
       if cc=1 then goto [fith]
       if cc=2 then goto [sixth]
[forth]       high=high2:x=low2:gosub [playSong]
       high=high4:x=low4:gosub [playSong]
       high=high5:x=low5:gosub [playSong]
[fith]       high=high4:x=low4:gosub [playSong]
       high=high4:x=low4:gosub [playSong]
[sixth]       high=high6:x=low6:gosub [playSong]

       high=high2:x=low2:gosub [playSong]
       high=high1:x=low1: gosub [playSong]


          print"Beethoven's Finger In A Box    Hey, even Beethoven had bad hair days!"
       timer 1500,[scratchPad]
    Wait


[quit]'stop note, close midi device, DLLs, window
    timer 0


    CallDLL #winmm, "midiOutClose", hMidiOut As ulong,_
        ret As ulong

    CallDLL#user32,"ReleaseDC",_
        Wnd As long,hDC As long,result As long

    End

[playSong]   'a keyboard key was pressed
    timer 0
     if x=high then return
     note=song(x)
     print note
        event=144   'event 144 = play on channel 1
    low=(note*256)+event
    velocity=127
    if note=1 then velocity=0
    hi=velocity*256*256
    dwMsg=low+hi
    CallDLL #winmm, "midiOutShortMsg",hMidiOut As ulong,_
        dwMsg As ulong, ret As ulong


    ry=rythem(x)
    ry=ry*2.5
    x=x+1

    timer ry,[stopNote]
    wait

[stopNote]

    event=144   'event 144 = play on channel 1
    low=(note*256)+event
    velocity=0
    hi=velocity*256*256
    dwMsg=low+hi
    CallDLL #winmm, "midiOutShortMsg",hMidiOut As ulong,_
        dwMsg As ulong, ret As ulong


       goto [playSong]
       wait


[doChange]'signal a voice change:
event=192 'event 192 = change
'voice=int(rnd(1)*128)
v=int(rnd(1)*21)+1
voice=vo(v)
print "Voice=";voice
velocity=127
low=(voice*256)+event
hi=velocity*256*256
dwMsg=low+hi
CallDLL #winmm, "midiOutShortMsg",hMidiOut As ulong,_
dwMsg As ulong, ret As ulong
RETURN


Home

Password Textbox API

Character Replacement

LB Isam Library

Beginning Games 2

Rubber Band Objects

WMLiberty Primer

LB Browser

Beginning Programming 5

INPUTTO Demo

Chase Button

Questionaire Wizard

MIDI Output Thoughts

MIDI-Tunes

Play MIDI DLL

Directory Search Function

Newsletter help

Index