SDL_Mixer : Mix_VolumeMusic() and windows native midi, a painful journey ; )

Now, in the days of yore there was a function that was called
"midiOutSetVolume". By the talentuous skills of the Mighty Wizards it had
been shaped into “native_midi_setvolume” (SDL_Mixer : native_midi_win32.c)
for the free people to enjoy the Music of the World; yet, the Evil One had
gathered all his powers full of malice and had marred the holy Source of
Creation and Lo ! the Mighty Wizards function had a strange behaviour.

Diving into the archives of the list, I could see that the Mix_VolumeMusic
in SDL_Mixer has been long discussed and has become sort of legendary.
However, I still had problems using it with midi files under Windows, and
I would like to “share” the strange things I encountered and the dirty fix
I tried…

First, the Behaviour :~~~~~~~~~~~~~~~~~~~~~~
I’m working with Windows 2000, and have a classic SBlive! SoundCard; I use
its MIDI device as my default MIDI Out device (nothing strange here).
Building a simple example with SDL_Mixer to play a midi file, I was so
happy at first to hear the Music of the World emanate from the craft of
the talented Mighty Wizards ! Exactly what I had dreamt of. However, after
trying Mix_VolumeMusic in every way, I got disappointed : nothing happened
at all. The sound volume was set irremediably to the Windows Midi Volume
Controller, and only the Black Magic of the Grey Slider of the Evil One
could do something for me :’( Worse, setting the Evil One’s device
(“Microsoft GS Wavetable SW Synth”) as my default device would make the
Wizards’ function work ! That was too much : the time for struggle had
come.

Then, the Quest for Malfunction :

Searching into the Cursed Scriptures of the Evil One :
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midi_functions.asp

I found the useful "midiOutGetNumDevs" and "midiOutGetDevCaps", which  
respectively give the number of midi out devices that could be used, and  
the capacities of each device. Applied to my machine, I got a MIDIOUTCAPS  
structure for each of my device. Here they are, with their field values  
(commented) :

-	capacities	{...}
	wMid	1 (MM_MICROSOFT)
	wPid	102 (MM_MSFT_WDMAUDIO_MIDIOUT)
	vDriverVersion	1280
+	szPname	0x0012fbec "Synth SB Live! MIDI"
	wTechnology	6 (MOD_WAVETABLE)
	wVoices	32
	wNotes	32
	wChannelMask	65535
	dwSupport	3 (MIDICAPS_VOLUME | MIDICAPS_LRVOLUME)

-	capacities	{...}
	wMid	1 (MM_MICROSOFT)
	wPid	102 (MM_MSFT_WDMAUDIO_MIDIOUT)
	vDriverVersion	1280
+	szPname	0x0012fbec "Microsoft GS Wavetable SW Synth"
	wTechnology	7 (MOD_SWSYNTH)
	wVoices	48
	wNotes	48
	wChannelMask	65535
	dwSupport	3 (MIDICAPS_VOLUME | MIDICAPS_LRVOLUME)

First, I was told that my SBlive was made by the Evil One  
(capacities.wMid==MM_MICROSOFT); gee, I should go and talk to my reseller  
about this. Then, capactities.dwSupport tells that "MIDICAPS_VOLUME |  
MIDICAPS_LRVOLUME". The Evil One explains in the Cursed Scriptures  
(http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midioutsetvolume.asp)  
that "Not all devices support volume changes. You can determine whether a  
device supports it by querying the device using the midiOutGetDevCaps  
function and the MIDICAPS_VOLUME flag.". Hey, hey, HEY : I do have this  
damn' flag ! It should work; but no : a call to "midiOutSetVolume" does  
NOTHING.

Now, the Path to the Light :
~~~~~~~~~~~~~~~~~~~~~~~~~~~~
At the edge of despair, often come the madest ideas.

I told myself : "What if I emulate the behaviour of the Grey Slider of the  
Evil One ?" ie, instead of setting the volume of the current opened  
device, I open the Evil One's device, change its volume, and close it ?  
That worked :). *_Changing the Evil One's device volume changes the volume  
of my SBlive! device._*. Incredible.

Now, the difficult stuff : finding the Evil One's device in an universal  
way. First option, use MIDIOUTCAPS.szPname and detect the Evil One's name  
presence. Bad idea : on XP for example, these strings are translated, it  
gives such things : "Microsoft GS Wavetable SW Synth" > (french) "Synth?.  
SW table de sons GS Mic"... the Evil One's name's cut to "Mic" due to the  
limited size of the szPname field.

Finally, I chose the following heuristics :
1) Make sure device supports MIDICAPS_VOLUME & MIDICAPS_LRVOLUME.
2) Make sure Manufacter id wMid == MM_MICROSOFT
3) Make sure Product id == MM_MSFT_WDMAUDIO_MIDIOUT
4) Make sure it is the software synthetizer : wTechnology==MOD_SWSYNTH


So here is the patch in SDL_mixer : native_midi_win32.c :
=========================================================

void native_midi_setvolume(int volume)
{
	int calcVolume;
	MIDIOUTCAPS capacities;
	HMIDIOUT  hMainVolumeDevice = 0;	
	unsigned int i = 0;
		
	if (volume > 128)
		volume = 128;
	if (volume < 0)
		volume = 0;
	calcVolume = (65535 * volume / 128);
	
	//Device loop
	for(i=midiOutGetNumDevs()-1;i>=0;i--)
	{
		//Get device capacities
		midiOutGetDevCaps(i,&capacities,sizeof(capacities));
		
		//Find microsoft sotfware synthetizer
		if((capacities.dwSupport&MIDICAPS_VOLUME)
		&& (capacities.dwSupport&MIDICAPS_LRVOLUME)
		&& (capacities.wMid==MM_MICROSOFT)
		&& (capacities.wPid==MM_MSFT_WDMAUDIO_MIDIOUT)
		&& (capacities.wTechnology==MOD_SWSYNTH))	
		{
			//Open device to change its volume
			midiOutOpen(&hMainVolumeDevice,i,0,0,CALLBACK_NULL);
			break;
		}
	}

	//Microsoft device found ? Change volume, and close it.
	if(hMainVolumeDevice)
	{
		midiOutSetVolume(hMainVolumeDevice, MAKELONG(calcVolume , calcVolume));
		midiOutClose(hMainVolumeDevice);
	}
	//Else, change current stream volume
	else if(hMidiStream)
		midiOutSetVolume((HMIDIOUT)hMidiStream, MAKELONG(calcVolume,calcVolume));
}

Remaining Questions :
~~~~~~~~~~~~~~~~~~~~~
I do not understand that behaviour between devices : has someone already  
observed this and migth be able to explain the phenomenon ? I'm totally  
lost :( Is the solution described here is good (or efficient ?) : it  
works, but it needs to open and close microsoft device at each volume  
change...

Thanks by advance for your opinion on this matter !

Ben

What does midiOutSetVolume() return when applied to the stream?

  1. Make sure device supports MIDICAPS_VOLUME & MIDICAPS_LRVOLUME.
  2. Make sure Manufacter id wMid == MM_MICROSOFT
  3. Make sure Product id == MM_MSFT_WDMAUDIO_MIDIOUT
  4. Make sure it is the software synthetizer : wTechnology==MOD_SWSYNTH

SDL_mixer is opening MIDI_MAPPER - what are the capabilities of that device
on your system?

-Sam Lantinga, Senior Software Engineer, Blizzard Entertainment

Thank you Sam for answering so quickly ! :slight_smile:

What does midiOutSetVolume() return when applied to the stream? […]
SDL_mixer is opening MIDI_MAPPER - what are the capabilities of that
device on your system?

While debugging, I noticed that MIDI_MAPPER corresponds to the MIDI out
device selected as default device in windows (Control Panel > Sounds and
Audio Device > Audio tab > MIDI music playback).

  1. When I select the MS Device, MIDI_MAPPER corresponds to it. Its
    capabilities are :
  • capabilities{MIDIOUTCAPS}
    wMid 1 (MM_MICROSOFT)
    wPid 102 (MM_MSFT_WDMAUDIO_MIDIOUT)
    vDriverVersion 1280
  • szPname 0x0012fbec "Microsoft GS Wavetable SW Synth"
    wTechnology 7 (MOD_SWSYNTH)
    wVoices 48
    wNotes 48
    wChannelMask 65535
    dwSupport3 (MIDICAPS_VOLUME | MIDICAPS_LRVOLUME)

Result : everything goes fine. midiOutSetVolume returns 0 which is
MMSYSERR_NOERROR.

  1. Then in the Control Panel, I select my Sound Blaster device. Opening
    MIDI_MAPPER opens this new selected device. Its capabilities are :
  • capabilities{MIDIOUTCAPS}
    wMid 1 (MM_MICROSOFT) (!!!)
    wPid 102 (MM_MSFT_WDMAUDIO_MIDIOUT) (!!!)
    vDriverVersion 1280
  • szPname 0x0012fbec "Synth SB Live! MIDI"
    wTechnology 6 (MOD_WAVETABLE)
    wVoices 32
    wNotes 32
    wChannelMask 65535
    dwSupport 3 (MIDICAPS_VOLUME | MIDICAPS_LRVOLUME)

As we can see, dwSupport tells that Volume change is available for this
device. However…

Result : Volume does not change. midiOutSetVolume returns 1 which is
simply “MMSYSERR_ERROR”. The funny thing is that msdn tells here :

http://msdn.microsoft.com/library/default.asp?url=/library/en-us/multimed/htm/_win32_midioutsetvolume.asp

“Returns MMSYSERR_NOERROR if successful or an error otherwise. Possible
error values include the following :
MMSYSERR_INVALHANDLE : The specified device handle is invalid.
MMSYSERR_NOMEM : The system is unable to allocate or lock memory.
MMSYSERR_NOTSUPPORTED : The function is not supported.”

That is to say, with MMSYSERR_ERROR we get no clue of what is happening :smiley:

Ben>> 1) Make sure device supports MIDICAPS_VOLUME & MIDICAPS_LRVOLUME.

  1. Make sure Manufacter id wMid == MM_MICROSOFT
  2. Make sure Product id == MM_MSFT_WDMAUDIO_MIDIOUT
  3. Make sure it is the software synthetizer : wTechnology==MOD_SWSYNTH

SDL_mixer is opening MIDI_MAPPER - what are the capabilities of that
device
on your system?

-Sam Lantinga, Senior Software Engineer, Blizzard Entertainment


SDL mailing list
SDL at libsdl.org
http://www.libsdl.org/mailman/listinfo/sdl

While debugging, I noticed that MIDI_MAPPER corresponds to the MIDI out
device selected as default device in windows (Control Panel > Sounds and
Audio Device > Audio tab > MIDI music playback).

  1. When I select the MS Device, MIDI_MAPPER corresponds to it. Its
    capabilities are :
  • capabilities{MIDIOUTCAPS}
    wMid 1 (MM_MICROSOFT)
    wPid 102 (MM_MSFT_WDMAUDIO_MIDIOUT)
    vDriverVersion 1280
  • szPname 0x0012fbec "Microsoft GS Wavetable SW Synth"
    wTechnology 7 (MOD_SWSYNTH)
    wVoices 48
    wNotes 48
    wChannelMask 65535
    dwSupport3 (MIDICAPS_VOLUME | MIDICAPS_LRVOLUME)

Result : everything goes fine. midiOutSetVolume returns 0 which is
MMSYSERR_NOERROR.

  1. Then in the Control Panel, I select my Sound Blaster device. Opening
    MIDI_MAPPER opens this new selected device. Its capabilities are :
  • capabilities{MIDIOUTCAPS}
    wMid 1 (MM_MICROSOFT) (!!!)
    wPid 102 (MM_MSFT_WDMAUDIO_MIDIOUT) (!!!)
    vDriverVersion 1280
  • szPname 0x0012fbec "Synth SB Live! MIDI"
    wTechnology 6 (MOD_WAVETABLE)
    wVoices 32
    wNotes 32
    wChannelMask 65535
    dwSupport 3 (MIDICAPS_VOLUME | MIDICAPS_LRVOLUME)

As we can see, dwSupport tells that Volume change is available for this
device. However…

Result : Volume does not change. midiOutSetVolume returns 1 which is
simply “MMSYSERR_ERROR”.

Can you test this with some other programs that use MIDI_MAPPER?
If you explicitly open the SB Live! MIDI device and change the
volume on that while it is the currently selected default device,
does that work?
What happens with the system midi volume slider when you have the
SB Live! device selected?

-Sam Lantinga, Senior Software Engineer, Blizzard Entertainment

Can you test this with some other programs that use MIDI_MAPPER?
If you explicitly open the SB Live! MIDI device and change the
volume on that while it is the currently selected default device,
does that work?
What happens with the system midi volume slider when you have the
SB Live! device selected?

After some tests : if if I open explicitly the SB Live! MIDI device,
midiOutSetVolume has the same behaviour. It returns 1 and the Volume does
not change in spite of the good value of the field capability structure
(I’ve verified that field again and again and again).

However, changing the volume with Windows volume controler “MIDI” slider,
the volume is affected : so, the slider is able to command the volume of
my SB, whereas the midiOutSetVolume function does not.

More puzzling, as I told in my first mail, if I open a midi stream with my
SB!live (selected as default one), and while the stream is playing I :

  • open the MS device with midiOutOpen
  • change the MS device volume with midiOutSetVolume
  • close it with midiOutClose

it does command the volume of the midi stream playing on the other
(SB!live) device.

There’s one last strange thing I noticed : when selecting my SB!live as
the default MIDI device (in Control Panel > Sounds and Audio Device >
Audio tab > MIDI music playback) the “Volume…” PushButton just below the
select device ComboBox BECOMES GREYED. I can’t explain that behaviour,
but it might be related to the midiOutSetVolume problem ?

Ben

PS : many thanks again, Sam :)On Tue, 31 Jan 2006, Sam Lantinga wrote:

I come finally to a solution quite satisfying, that is to say patching
SDL_mixer so that the midi volume function changes the volume of ALL
midiout devices that have the MIDICAPS_VOLUME set in their capabilities
(MIDIOUTCAPS.dwSupport).

Thus, if there’s a “master” device for midi volume, which seems to happen
on my machine, it is also affected by a call to Mix_VolumeMusic and
everything is fine. Thus, my solution :

SDL_Mixer : native_midi_win32.c :=================================

void native_midi_setvolume(int volume)
{
int calcVolume;
MIDIOUTCAPS capabilities;
int i = 0;

if (volume > 128)
	volume = 128;
if (volume < 0)
	volume = 0;
calcVolume = (65535 * volume / 128);

//Device loop
for(i=0;i<midiOutGetNumDevs();i++)
{
	//Get device capabilities
	midiOutGetDevCaps(i,&capabilities,sizeof(capabilities));
	
	//Adjust volume on this candidate
	if((capabilities.dwSupport&MIDICAPS_VOLUME))
		midiOutSetVolume((HMIDIOUT)i, MAKELONG(calcVolume , calcVolume));
}

}

My remaining questions are : do you think is this method safe ? robust ?
resource consuming ? or simply OK ?

In any case, it solved my problems :slight_smile:

Ben

On Wed, 01 Feb 2006 17:37:06 +0100, Benjamin Babut <@Benjamin_Babut> wrote:

On Tue, 31 Jan 2006, Sam Lantinga wrote:

Can you test this with some other programs that use MIDI_MAPPER?
If you explicitly open the SB Live! MIDI device and change the
volume on that while it is the currently selected default device,
does that work?
What happens with the system midi volume slider when you have the
SB Live! device selected?

After some tests : if if I open explicitly the SB Live! MIDI device,
midiOutSetVolume has the same behaviour. It returns 1 and the Volume does
not change in spite of the good value of the field capability structure
(I’ve verified that field again and again and again).

However, changing the volume with Windows volume controler “MIDI” slider,
the volume is affected : so, the slider is able to command the volume of
my SB, whereas the midiOutSetVolume function does not.

I come finally to a solution quite satisfying, that is to say patching
SDL_mixer so that the midi volume function changes the volume of ALL
midiout devices that have the MIDICAPS_VOLUME set in their capabilities
(MIDIOUTCAPS.dwSupport).

It sounds a bit like overkill, but I’m not very familiar with MIDI use
on Windows. Is this what programs do in general?

Ben, can you find other MIDI programs out there and see how many of them
have problems with your setup?

Thanks!
-Sam Lantinga, Senior Software Engineer, Blizzard Entertainment