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
Midi_boink also has allowed me to publish several MIDI demos that he posted recently. There are three:
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
'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
'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