Modding talk:Audio
Revision as of 05:21, 17 October 2021 by Pathoschild (talk | contribs) (→Track list export: update to export wavebank info)
Track list export
The initial track list was exported with this code, which can be run as a C# mod:
source code |
---|
/// <summary>The main entry point for the mod.</summary>
public class ModEntry : Mod
{
/*********
** Public methods
*********/
/// <inheritdoc />
public override void Entry(IModHelper helper)
{
helper.Events.GameLoop.GameLaunched += (_, _) =>
{
StringBuilder output = new();
foreach (var categoryGroup in this.GetTracks().GroupBy(p => p.GetCategoryName()).OrderBy(p => p.Key))
{
output.AppendLine($"==={categoryGroup.Key}===");
output.AppendLine("{| class=\"wikitable sortable\"");
output.AppendLine("|-\n! name\n! wavebank\n! soundbank ID\n! description");
foreach (TrackInfo track in categoryGroup.OrderBy(p => p.Name).ThenBy(p => p.Index))
output.AppendLine($"|-\n| <tt>{track.Name}</tt>\n| <tt>{track.GetWavebankName()}</tt>\n| data-sort-value=\"{track.Index}\"| <tt>{track.GetSoundbankId()}</tt>\n| ");
output.AppendLine("|}");
output.AppendLine();
}
string result = output.ToString();
this.Monitor.Log(result, LogLevel.Info);
};
}
/*********
** Private methods
*********/
/// <summary>Extract the music/sound tracks from the game's soundbank.</summary>
private IEnumerable<TrackInfo> GetTracks()
{
SoundBank soundBank = this.Helper.Reflection.GetField<SoundBank>(Game1.soundBank, "soundBank").GetValue();
IEnumerable<CueDefinition> cues = this.Helper.Reflection.GetField<Dictionary<string, CueDefinition>>(soundBank, "_cues").GetValue().Values;
foreach (CueDefinition cue in cues)
{
foreach (XactSoundBankSound sound in cue.sounds)
{
// simple sound
if (!sound.complexSound)
{
yield return new TrackInfo(
WavebankIndex: sound.waveBankIndex,
CategoryId: sound.categoryID,
Name: cue.name,
Index: sound.trackIndex
);
continue;
}
// complex sound
bool hasVariants = false;
if (sound.soundClips != null)
{
foreach (XactClip clip in sound.soundClips)
{
foreach (ClipEvent rawClipEvent in clip.clipEvents)
{
if (rawClipEvent is not PlayWaveEvent clipEvent)
{
this.Monitor.Log($"Unexpected clip event type '{rawClipEvent.GetType().FullName}'.", LogLevel.Error);
continue;
}
foreach (PlayWaveVariant variant in clipEvent.GetVariants())
{
hasVariants = true;
yield return new TrackInfo(
WavebankIndex: variant.waveBank,
CategoryId: sound.categoryID,
Name: cue.name,
Index: variant.track
);
}
}
}
}
// invalid sound, should never happen
if (!hasVariants)
this.Monitor.Log($"Complex sound '{cue.name}' unexpectedly has no variants.", LogLevel.Error);
}
}
}
}
/// <summary>A sound or music track in a wavebank.</summary>
/// <param name="WavebankIndex">The wavebank which contains the track.</param>
/// <param name="CategoryId">The sound or music category.</param>
/// <param name="Name">The sound name used in the game code.</param>
/// <param name="Index">The offset index in the raw soundbank.</param>
public record TrackInfo(int WavebankIndex, uint CategoryId, string Name, int Index)
{
/// <summary>Get a human-readable name for the category ID.</summary>
public string GetCategoryName()
{
return this.CategoryId switch
{
2 => "Music",
3 => "Sound",
4 => "Music (ambient)",
5 => "Footsteps",
_ => this.CategoryId.ToString()
};
}
/// <summary>Get a human-readable for the wavebank index.</summary>
public string GetWavebankName()
{
return this.WavebankIndex switch
{
0 => "Wavebank",
1 => "Wavebank(1.4)",
_ => this.WavebankIndex.ToString()
};
}
/// <summary>Get the hexadecimal soundbank ID which matches the filenames exported by unxwb.</summary>
public string GetSoundbankId()
{
return this.Index
.ToString("X")
.ToLower()
.PadLeft(8, '0');
}
}
|
The game data only has the numerical category IDs, but I confirmed the internal category names with one of the game developers:
id | name | my notes |
---|---|---|
1 | Default | seems to be unused. |
2 | Music | |
3 | Sound | |
4 | Ambient | listed on the wiki as "Music (ambient)" just to group the music tracks. |
5 | Footsteps |
—Pathoschild (talk) 05:21, 17 October 2021 (UTC)