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