by Dark Pulse » Sat Feb 16, 2019 3:19 pm
I've been kicking around soundfont-related stuff in my head over the last few days.
Normally, users are allowed to select their own sound fonts. This is good for when a WAD has general (or no) custom music that's MIDI-based, but not intended to be only played with a specific soundfont.
However, this also presents some extra steps the end-user has to do if they play a level set where there are specific MIDIs that demand a specific soundfont, and won't sound right any other way. It also presents some annoying problems if the user has to (for some reason) switch soundfonts mid-experience - play a level that needs it that's partway through a level set, and you have to set it once the level comes up, then un-set it once you're done with the level that uses it. And god forbid if you have to do this multiple times in your playthrough...
What I propose, then, is a system where a level creator/packager can set it so that included SF2s gets used automatically by ZDoom with the presence of some info files included in the WAD/PK3/PK7. This way the packager can set all of this up for the end-user so they don't have to mess around with soundfonts, or accidentally forget to undo a soundfont change if they play a mod where using a soundfont is necessary for the music, but then play something else which will sound amusingly wrong with the "wrong" soundfont. ZDoom can then handle the swapping of soundfonts as needed, and the user can simply plug and play.
In terms of detection, this would work as follows:
- ZDoom reads the user's default soundfont/etc. settings, which sets a baseline universal setting (including if no embedded soundfonts wind up getting used). This is what the engine does already (I presume, anyway).
- ZDoom then scans the WAD/PK3/PK7 for a specific file that would contain auto-setting soundfont information (tentatively called SF2INFO).
- If not found, proceed as normal - user's sound settings, including soundfont, are used.
- If found, the engine checks and sets proper information for what soundfonts are loaded for whatever maps are selected in the SF2INFO, overriding the user's default soundfont settings for the level(s) as directed by the SF2INFO.
- This way, the MIDI-based tracks that use that soundfont, use their proper soundfont, without the end-user having to futz around with soundfonts, because it will only change them as directed by the SF2INFO - any map that is not defined by it, does not get adjusted by it, and we automatically fall back to the user's settings as the baseline.
- Engine execution then proceeds as usual.
A SF2INFO lump is based loosely upon the MUSINFO lump, and would look something like this:
Code: Select all
<Map Identifier>
<Soundfont to be used>
<Second Map Identifier>
<Soundfont to be used>
<...>
Some examples.
Let's say we want two specific levels, and
ONLY two specific levels, to use an embedded soundfont, but nothing else. That'd look like this:
Code: Select all
MAP01
MySoundfont.sf2
MAP14
MySecondSoundfont.sf2
This would set MAP01 to use a specific soundfont, and MAP14 to use a second soundfont entirely. However, from MAP02 on to MAP13, and from MAP15 onward, no specific soundfont would get used, and we would revert to the user's general soundfont settings.
Ranges should be possible, as well:
Code: Select all
MAP01-16
KickAssSoundfont.sf2
MAP17-32
EvilSoundfont.sf2
This would set all maps from MAP01-MAP16 to use one soundfont, and 17-32 to use a second. The user's normal soundfont options don't get used in this level set at all (assuming there's only 32 maps like most total replacements, anyway - in this example, a theoretical MAP33 would actually use the user's default soundfont settings, since it's not defined.)
What about if you want to use the same soundfonts for multiple sections? That works too:
Code: Select all
MAP01-08, MAP17-24
DimensionXSoundfont.sf2
MAP09-16
DimensionYSoundfont.sf2
MAP25-32
DimensionZSoundfont.sf2
If you need just one Soundfont to apply to every single level, something like this could work without needing to set a whole range or individually-defined maps:
It should account for level sets that have non-standard map naming conventions as well:
Code: Select all
Z1M*
Episode1Soundfont.sf2
Z2M*
Episode2Soundfont.sf2
Universally naming stuff using Doom 1/Heretic map naming conventions also can be wildcarded just fine:
The only thing wildcards can't really cover is if the mapper decides to name every map a unique name with no particular numerical order. In this case, you just kind of go for something like this, and hope for the best:
...but if you do that, probably more than the engine will hate you. Your players, for starters.
This would break down via search paths as thus:
- WAD: It'd be looking for lumps named whatever you've embedded them as in the WAD. You're way more limited on filename here due to the limitations of the WAD format to 8 characters with no extension. Indeed, being lumps, they won't have an extension either - so it's possible that we could say that any SF2INFO that doesn't have a .sf2 at the end of its Soundfont definition(s) is assumed to be a lump inside the WAD itself.
- PK3/PK7: Gain a new top-level namespace, soundfonts/, where your SF2s are expected to be located. Obviously it will be expecting a SF2INFO lump inside the root directory if you go this route.
A few minor hangups:
- If missing but defined by SF2INFO: Naturally, we either throw an error if it's missing, or use the user-defined soundfont, but warn that the soundfont is missing and that your ears are possibly about to get raped.
- The presence of a SF2INFO means that ZDoom is expecting creator-defined soundfonts; if SF2INFO is missing, this is how we tell apart that the user is simply playing a level set that had no specific soundfonts intended in mind, and so no need to warn the user there.
- Conflicting Entries: ZDoom would need to detect if the user accidentally sets the same map (or maps) to use two different soundfonts (since it'd have no idea which soundfont to use otherwise). For example, if the user sets MAP07 to have its own soundfont, but there's also a second MAP07 definition, or a broad-scope definition such as MAP01-08 or MAP* that also is creating a second definition for that.
- A possibly "lazy way around it" if there's not two specific map definitions would be to either have it so that a map-specific definition overrules a broad-scope one (in other words, it would use the hand-defined MAP07 one and ignore the broad MAP01-08 or MAP* one for that map), or to have it be heirarchy-based - the first definition is considered the "base" and any definitions written after that are considered to overwrite that, just like how PWADs overwrite IWADs/earlier PWADs when used.
- Hard-defining two specific level entries would probably deserve either a warning (at a minimum) or possibly an abort.
- This does nothing for DLS or PATs, which only the TiMidity++ backend can use - FluidSynth is out of luck. However, creation/conversion of soundfonts is beyond the scope of what this is intended to address.
- Finally, if there's more than one SF2INFO, there is probably no manual way to reserve the conflict. But seeing as that means the end-user is trying to load two level packs at the same time and there is a high likelihood of conflict between the map data themselves, it probably should just get handled by the standard overriding rules - the file loaded last gets precedence. Having two actual SF2INFOs in one WAD/PK3/PK7 should be impossible, after all. And if for some reason they don't conflict but the SF2INFOs do, all that'd really be needed to fix it is writing up a merged SF2INFO that would get loaded after all the levels - but again, the odds of that are pretty small in my book (not to mention not ZDoom's problem, either).
Anyway, sorry for the long post, but I wanted to try to establish the rules they obey, more or less how they work, and so on. Hopefully you guys agree this would be good to pick up.
If you've got any other questions, by all means, I'll try to answer them.
I've been kicking around soundfont-related stuff in my head over the last few days.
Normally, users are allowed to select their own sound fonts. This is good for when a WAD has general (or no) custom music that's MIDI-based, but not intended to be only played with a specific soundfont.
However, this also presents some extra steps the end-user has to do if they play a level set where there are specific MIDIs that demand a specific soundfont, and won't sound right any other way. It also presents some annoying problems if the user has to (for some reason) switch soundfonts mid-experience - play a level that needs it that's partway through a level set, and you have to set it once the level comes up, then un-set it once you're done with the level that uses it. And god forbid if you have to do this multiple times in your playthrough...
What I propose, then, is a system where a level creator/packager can set it so that included SF2s gets used automatically by ZDoom with the presence of some info files included in the WAD/PK3/PK7. This way the packager can set all of this up for the end-user so they don't have to mess around with soundfonts, or accidentally forget to undo a soundfont change if they play a mod where using a soundfont is necessary for the music, but then play something else which will sound amusingly wrong with the "wrong" soundfont. ZDoom can then handle the swapping of soundfonts as needed, and the user can simply plug and play.
In terms of detection, this would work as follows:
[list=1]
[*]ZDoom reads the user's default soundfont/etc. settings, which sets a baseline universal setting (including if no embedded soundfonts wind up getting used). This is what the engine does already (I presume, anyway).
[*]ZDoom then scans the WAD/PK3/PK7 for a specific file that would contain auto-setting soundfont information (tentatively called [b]SF2INFO[/b]).
[list]
[*]If not found, proceed as normal - user's sound settings, including soundfont, are used.[/list]
[*]If found, the engine checks and sets proper information for what soundfonts are loaded for whatever maps are selected in the SF2INFO, overriding the user's default soundfont settings for the level(s) as directed by the SF2INFO.
[list]
[*]This way, the MIDI-based tracks that use that soundfont, use their proper soundfont, without the end-user having to futz around with soundfonts, because it will only change them as directed by the SF2INFO - any map that is not defined by it, does not get adjusted by it, and we automatically fall back to the user's settings as the baseline.[/list]
[*]Engine execution then proceeds as usual.[/list]
A SF2INFO lump is based loosely upon the MUSINFO lump, and would look something like this:
[code]
<Map Identifier>
<Soundfont to be used>
<Second Map Identifier>
<Soundfont to be used>
<...>
[/code]
Some examples.
Let's say we want two specific levels, and [b][i][u]ONLY[/u][/i][/b] two specific levels, to use an embedded soundfont, but nothing else. That'd look like this:
[code]
MAP01
MySoundfont.sf2
MAP14
MySecondSoundfont.sf2
[/code]
This would set MAP01 to use a specific soundfont, and MAP14 to use a second soundfont entirely. However, from MAP02 on to MAP13, and from MAP15 onward, no specific soundfont would get used, and we would revert to the user's general soundfont settings.
Ranges should be possible, as well:
[code]
MAP01-16
KickAssSoundfont.sf2
MAP17-32
EvilSoundfont.sf2
[/code]
This would set all maps from MAP01-MAP16 to use one soundfont, and 17-32 to use a second. The user's normal soundfont options don't get used in this level set at all (assuming there's only 32 maps like most total replacements, anyway - in this example, a theoretical MAP33 would actually use the user's default soundfont settings, since it's not defined.)
What about if you want to use the same soundfonts for multiple sections? That works too:
[code]
MAP01-08, MAP17-24
DimensionXSoundfont.sf2
MAP09-16
DimensionYSoundfont.sf2
MAP25-32
DimensionZSoundfont.sf2
[/code]
If you need just one Soundfont to apply to every single level, something like this could work without needing to set a whole range or individually-defined maps:
[code]
MAP*
UniversalSoundfont.sf2
[/code]
It should account for level sets that have non-standard map naming conventions as well:
[code]
Z1M*
Episode1Soundfont.sf2
Z2M*
Episode2Soundfont.sf2
[/code]
Universally naming stuff using Doom 1/Heretic map naming conventions also can be wildcarded just fine:
[code]
E*M*
UniversalDoomSoundfont.sf2
[/code]
The only thing wildcards can't really cover is if the mapper decides to name every map a unique name with no particular numerical order. In this case, you just kind of go for something like this, and hope for the best:
[code]
*
ThisCoversAllMapsHopefully.sf2
[/code]
...but if you do that, probably more than the engine will hate you. Your players, for starters.
This would break down via search paths as thus:
[list]
[*][b]WAD[/b]: It'd be looking for lumps named whatever you've embedded them as in the WAD. You're way more limited on filename here due to the limitations of the WAD format to 8 characters with no extension. Indeed, being lumps, they won't have an extension either - so it's possible that we could say that any SF2INFO that doesn't have a .sf2 at the end of its Soundfont definition(s) is assumed to be a lump inside the WAD itself.
[*][b]PK3/PK7:[/b] Gain a new top-level namespace, [b]soundfonts/[/b], where your SF2s are expected to be located. Obviously it will be expecting a SF2INFO lump inside the root directory if you go this route.[/list]
A few minor hangups:
[list=1]
[*]If missing but defined by SF2INFO: Naturally, we either throw an error if it's missing, or use the user-defined soundfont, but warn that the soundfont is missing and that your ears are possibly about to get raped.
[list]
[*]The presence of a SF2INFO means that ZDoom is expecting creator-defined soundfonts; if SF2INFO is missing, this is how we tell apart that the user is simply playing a level set that had no specific soundfonts intended in mind, and so no need to warn the user there.[/list]
[*]Conflicting Entries: ZDoom would need to detect if the user accidentally sets the same map (or maps) to use two different soundfonts (since it'd have no idea which soundfont to use otherwise). For example, if the user sets MAP07 to have its own soundfont, but there's also a second MAP07 definition, or a broad-scope definition such as MAP01-08 or MAP* that also is creating a second definition for that.
[list]
[*]A possibly "lazy way around it" if there's not two specific map definitions would be to either have it so that a map-specific definition overrules a broad-scope one (in other words, it would use the hand-defined MAP07 one and ignore the broad MAP01-08 or MAP* one for that map), or to have it be heirarchy-based - the first definition is considered the "base" and any definitions written after that are considered to overwrite that, just like how PWADs overwrite IWADs/earlier PWADs when used.
[*]Hard-defining two specific level entries would probably deserve either a warning (at a minimum) or possibly an abort.[/list]
[*]This does nothing for DLS or PATs, which only the TiMidity++ backend can use - FluidSynth is out of luck. However, creation/conversion of soundfonts is beyond the scope of what this is intended to address.
[*]Finally, if there's more than one SF2INFO, there is probably no manual way to reserve the conflict. But seeing as that means the end-user is trying to load two level packs at the same time and there is a high likelihood of conflict between the map data themselves, it probably should just get handled by the standard overriding rules - the file loaded last gets precedence. Having two actual SF2INFOs in one WAD/PK3/PK7 should be impossible, after all. And if for some reason they don't conflict but the SF2INFOs do, all that'd really be needed to fix it is writing up a merged SF2INFO that would get loaded after all the levels - but again, the odds of that are pretty small in my book (not to mention not ZDoom's problem, either).[/list]
Anyway, sorry for the long post, but I wanted to try to establish the rules they obey, more or less how they work, and so on. Hopefully you guys agree this would be good to pick up.
If you've got any other questions, by all means, I'll try to answer them.