Eventually I want to write many notes of varing durations.
Can't figure out how to include the time variations from
note to note.

Below is the code to write 1 Note using Midi.
Needless to say it doesn't work...

include \masm32\include\winmm.inc
includelib \masm32\lib\winmm.lib

notes dd 00403e90h ; note D4

376 push MIDI_MAPPER
377 pop

378 invoke midiOutOpen, offset lphmo, , NULL, 0 CALLBACK_NULL
(Why would I need a CallBack function)????
379 invoke MidiOutShortMsg, hmo, dword ptr ; D4
380 invoke midiOutReset, hmo
381 invoke midiOutClose, hmo

Errors generated:

378 error A2006: undefined symbol : uDeviceID
378 error A2114: invoke argument type mismatch: argument : 2
378 error A2006: undefined symbol : lphmo
378 error A2114: invoke argument type mismatch: argument: 1
379 error A2006: undefined symbol: hmo
379 error A2114: invoke argument type mismatch; argument: 1
380 error A2006: undefined symbol: hmo
380 error A2114: invoke argument type mismatch: argument: 1
381 error A2006: undefined symbol: hmo
381 error A2114: invoke argument type mismatch: argument: 1
377 error A2006: undefined symbol: MIDI_MAPPER

Thanks for any help...
Posted on 2003-07-24 15:55:19 by shankle
Hi Shankle,

Notice the pointer to receive the handle to the open MIDI device, offset lphmo, you need to declare it as lphmo in the subsequent calls, not hmo. A couple other points, undefined symbol: MIDI_MAPPER , this should be defined in windows.inc, I'm surprised you got the error. As for the other variables you should declare them like this:

lphmo HANDLE ?
uDeviceID DWORD ?

I meant to mention about the callback in my earlier post, you don't really need it, only if you want to intercept messages when the midi device is opened (MM_MOM_OPEN), finished playing (may be important for timing purposes), or closed. You can simply call it like this, where -1 is the default MIDI_MAPPER device.

invoke midiOutOpen, offset lphmo, -1, 0,0,0 ; Open default MIDI Out device


You might use a callback if you're trying to send System Exclusive messages, which can be used to send specific controlling messages to specific midi devices (i.e. Roland), but then you'd probably be handling real midi keyboard input and midiIn (MIM_*) messages as well.

What's interesting is that, in theory at least, you can also send raw midi streams which include note durations and other musical information, using midiOutLongMsg and filling a MIDIHDR structure. I believe this would avoid using messy timers and a bunch of midiOutShortMsg calls to try to play a sequence of notes to resemble something remotely musical!

I haven't written any code to send a raw midi dump yet, though the information seems to be around. What I did with the code was to create a virtual midi keyboard, mapped to the PC keyboard, as well as to buttons resembling a two octave piano keyboard. Add a few effects sliders, knobs and buttons to twiddle with and it was kind of fun, but the project has been gathering dust recently.

If you do want to try stringing together a bunch of notes/chords (I figured the opening line of Fur Elise might be good) using midiOutShortMsg, careful control of the note durations with a high res timer would be necessary. I may have a paper or two on high res timers if you want them. I think you can forget about using the midiStream* API's to send a buffer of midi data, according to the newsgroups this was apparently an experiment of MicroSnort's they stopped developing, might have worked on Win95, I couldn't get them working in 98SE.


Here's a bit of code for sending a SysEx message (the Master Volume message is an example from some text and the code is only test code plus you'd have to specify the Device Control).
[size=12]

; Master Volume - This adjusts a device's master volume.
; Remember that in a multitimbral device, the Volume controller
; messages are used to control the volumes of the individual Parts.
; So, we need some message to control Master Volume. Here it is.

; 0xF0 SysEx
; 0x7F Realtime
; 0x7F The SysEx channel. Could be from 0x00 to 0x7F.
; Here we set it to "disregard channel".
; 0x04 Sub-ID -- Device Control
; 0x01 Sub-ID2 -- Master Volume
; 0xLL Bits 0 to 6 of a 14-bit volume
; 0xMM Bits 7 to 13 of a 14-bit volume
; 0xF7 End of SysEx

SysExMessage1 db 0F0h, 7Fh, 7Fh, 04h, 01h, 7Fh, 7Fh, 0F7h

...

;-----------------------
; Prepare Header
;-----------------------

push offset SysExMessage1
pop midiHdr.lpData ; Pointer to MIDI data

mov midiHdr.dwFlags, 0
mov midiHdr.dwBufferiLength, 4*2
mov midiHdr.dwBytesRecorded, 4*2

invoke midiOutPrepareHeader, lphmo, offset midiHdr, SIZEOF midiHdr
invoke midiOutLongMsg, lphmo, offset midiHdr, SIZEOF midiHdr

invoke Sleep, 10 ;

invoke midiOutUnprepareHeader, lphmo, offset midiHdr, SIZEOF midiHdr
invoke midiOutReset, lphmo
invoke midiOutClose, lphmo
[/SIZE]


And here's a Callback proc skeleton:
[size=12]

;======MidiOut_Callback proc======
MidiOut_Callback PROC hwnd:DWORD, wmsg:DWORD, inst:DWORD,\
wparam:DWORD, lparam:DWORD

.IF wmsg == MOM_OPEN
; sent to a window when a MIDI output device is opened
mov eax, wparam ; Handle to the MIDI output device

.ELSEIF wmsg == MOM_DONE
; sent to a window when the specified MIDI
; system-exclusive or stream buffer has been played
; and is being returned to the application.

mov eax, wparam
; Handle to the MIDI output device that played the buffer
mov eax, lparam
; Pointer to a MIDIHDR structure identifying the buffer

.ELSEIF wmsg == MOM_CLOSE

.ENDIF
ret
MidiOut_Callback endp
;======End MidiOut_Callback proc======
[/SIZE]


Kayaker
Posted on 2003-07-25 00:24:45 by Kayaker
Hi Kayaker,
Thanks so much for your help. Am in the process of checking out
your latest message. Will probably have more questions as soon
as I can make the necessary corrections.
The web sight you gave in another message helped a lot but I
have a long way to go with midi.

Jack
Posted on 2003-07-25 05:52:06 by shankle
A test code I used a few months ago:



MIDIpack macro channel,command,a,b
exitm <(command*16)+channel+(a*256)+(b*65536)>
endm
.data
hMIDIout dd 0
.code
CreateMIDIout proc uses eax ebx ecx edx
invoke midiOutOpen,addr hMIDIout,MIDI_MAPPER,0,hinst,CALLBACK_NULL
invoke Sleep,100
invoke midiOutShortMsg,hMIDIout,MIDIpack(192,0,1,0)
invoke midiOutShortMsg,hMIDIout,MIDIpack(144,0,67,127)
ret
CreateMIDIout endp


MIDI is a lot easier than it seems! I created a complete sequencer in 20 days, as before that I didn't know MIDI file format and messages. You can see the sequencer at the demo version of Dreamer (my proggie) that is at my site (button below) ;)
maybe I can give a few ideas, too :grin:
Posted on 2003-07-25 12:05:21 by Ultrano
I have included an attachment with a snipit of MIDI code that WORKS...
HOWEVER, the sound (which I think is controlled by 07fh) is 33 to 50%
to soft..... Don't know what to do about that!

162 = a 16th note
325 = an 8th note
650 = a quarter note

Don't know how to represent a rest.

Thanks for any help
Posted on 2003-07-25 20:26:48 by shankle
before each note_on you reset the MIDI device :confused: . I think this is your mistake. I can't test it myself now, but I think you have to replace all 'reset'-s to "note_off" . Maybe the resetting at your soundcard leads also to setting the midi synth's master volume to 0, and restoring it to 100% in several hundred milliseconds. Thus, your first note will sound as if it has attack=100% , which , really produces 'soft' notes. Sending a note_off doesn't need the second parameter to be the same as it was in note_on ;)
Posted on 2003-07-26 02:19:11 by Ultrano
I have included a revised snipit of the midi code.
I added code for the midi header from Kayaker which he said was
untested. I ran it and the sound stayed the same(you have to remember
I don't know what I am doing) He said I would have to make some change
to "DEVICE CONTROL" in the header but that completely loses me.

Thanks for any help...
Posted on 2003-07-26 08:22:16 by shankle
Ultrano has a point about the master midi volume may be reset by your code, I noticed a similar effect at times, check your mixer controls where I found you should have the Midi volume set fairly high for testing. To use Note Off instead of the midiOutReset calls, just replace the 7Fh volume byte with 00h and send another midiOutShortMsg with it after your Sleep interval.

You may get better sound as well if you choose another midi device over the Midi Mapper. To get a list of the MIDI devices and their capabilities you can use midiOutGetNumDevs and midiOutGetDevCaps, there is a MIDIOUTCAPS structure that gives the name of the midi device as well as the wMid (Manufacturer ID) and wPid (Product Identifier). I *think* one of these is what is required by the SysEx message Device Control identifier, but don't quote me on it, heheh. Manufacturer and Product IDs are discussed in the SDK under the MIDIOUTCAPS description.

It's up to the midi device to interpret what you send it, garbage will be ignored, so you generally have no feedback from the SysEx messages as to whether they 'worked' or not unless you really know what you're sending. There are several utilities around specifically designed for sending SysEx messages you can experiment with sometime.

Note that midiOutGetNumDevs returns the number of MIDI output devices, which also happens to coincide with the value of , i.e. if you
push 3
pop
you'll choose the 3rd midi device listed to receive the midiOutShortMsg, chances are you only have a few other midi devices provided by your sound card, so you can try them all just by changing to 0,1,2,...

You should watch the copy/pasting as well, you need to invoke midiOutUnprepareHeader after midiOutLongMsg, not a second call to midiOutPrepareHeader.

Kayaker
Posted on 2003-07-26 13:51:10 by Kayaker
:grin: I got my previous code wrong. Maybe I had modified it after I stopped using it. This works fine:


.data
hMIDIout dd 0
.code
MIDIpack macro channel,command,a,b
exitm <(channel*16)+command+(a*256)+(b*65536)>
endm ; had swapped places of channel and command


TestBakaMIDI proc
local hinst,i
invoke GetModuleHandle,0
mov hinst,eax
invoke midiOutOpen,addr hMIDIout,\
MIDI_MAPPER,0,hinst,CALLBACK_NULL
invoke Sleep,200
mov i,50
.while i<60
mov eax,i
shl eax,8
add eax,MIDIpack(9,0,0,127)
invoke midiOutShortMsg,hMIDIout,eax
invoke Sleep,260
mov eax,i
shl eax,8
add eax,MIDIpack(8,0,0,127)
invoke midiOutShortMsg,hMIDIout,eax
inc i
.endw
invoke midiOutClose,hMIDIout
;print 'ok'
ret
TestBakaMIDI endp


I also tested with the NoteOff replaced by Reset. On my YMF724 I get glitches from Reset, but I don't get the notes softer. Synth is different, so I can't help with that.
btw, why don't you replace all the lines of code for sending different notes into an automated task - supply an array or two, and its size.
Posted on 2003-07-26 14:30:22 by Ultrano
:grin: nice melody :alright:

I decided to help with making the function I mentioned above. Have fun:


.data
D5 equ 4ah ;pitches
F4 equ 41h
A4 equ 45h
G4 equ 43h
C5 equ 48h
b4 equ 46h ; this is Bb4

L1 equ 0 ;162 ;durations
L2 equ 1 ;325
L3 equ 2 ;650

notes db F4,A4,A4,F4,A4,G4,F4,C5,b4,G4,A4,b4,A4,C5,A4,C5,A4,G4,A4,b4,F4
durations db L2,L2,L2,L2,L1,L1,L2,L3,L2,L2,L2,L2,L1,L1,L2,L3,L2,L2,L2,L2,L1
notes_num equ ($-notes)/2
.code

PlayMahNotes proc
xor ecx,ecx
.while ecx<notes_num
push ecx
movzx eax,notes[ecx]
shl eax,8
add eax,MIDIpack(9,0,0,127) ; note_on, velocity is 127
invoke midiOutShortMsg,hMIDIout,eax
pop ecx
push ecx
mov eax,162
movzx ecx,durations[ecx]
shl eax,cl
invoke Sleep,eax
pop ecx
push ecx
movzx eax,notes[ecx]
shl eax,8
add eax,MIDIpack(8,0,0,0) ; note off
invoke midiOutShortMsg,hMIDIout,eax

pop ecx
inc ecx
.endw


ret
PlayMahNotes endp
Posted on 2003-07-26 14:46:57 by Ultrano