Difference between revisions of "Modding talk:Audio"
Jump to navigation
Jump to search
Pathoschild (talk | contribs) (→Track list export: update code) |
Pathoschild (talk | contribs) (→Track list export: rework code a bit, add category info) |
||
Line 24: | Line 24: | ||
{ | { | ||
string soundIdLabel = sound.Id.ToString("X").ToLower().PadLeft(8, '0'); | string soundIdLabel = sound.Id.ToString("X").ToLower().PadLeft(8, '0'); | ||
+ | |||
output.AppendLine($"|-\n| <tt>{sound.Name}</tt>\n| data-sort-value=\"{sound.Id}\"| <tt>{soundIdLabel}</tt>\n| "); | output.AppendLine($"|-\n| <tt>{sound.Name}</tt>\n| data-sort-value=\"{sound.Id}\"| <tt>{soundIdLabel}</tt>\n| "); | ||
} | } | ||
Line 50: | Line 51: | ||
foreach (XactSoundBankSound sound in entry.Value.sounds) | foreach (XactSoundBankSound sound in entry.Value.sounds) | ||
{ | { | ||
− | |||
− | |||
− | |||
string name = entry.Key; | string name = entry.Key; | ||
string category = this.GetCategoryName(sound.categoryID); | string category = this.GetCategoryName(sound.categoryID); | ||
− | |||
− | |||
− | // | + | // simple sound |
+ | if (!sound.complexSound) | ||
+ | { | ||
+ | using SoundEffectInstance instance = sound.GetSimpleSoundInstance(); | ||
+ | |||
+ | yield return new SoundInfo | ||
+ | { | ||
+ | Category = category, | ||
+ | Name = name, | ||
+ | Id = sound.trackIndex | ||
+ | }; | ||
+ | continue; | ||
+ | } | ||
+ | |||
+ | // complex sound | ||
+ | bool hasVariants = false; | ||
if (sound.soundClips != null) | if (sound.soundClips != null) | ||
{ | { | ||
foreach (XactClip clip in sound.soundClips) | foreach (XactClip clip in sound.soundClips) | ||
{ | { | ||
− | foreach ( | + | foreach (ClipEvent rawClipEvent in clip.clipEvents) |
{ | { | ||
if (rawClipEvent is not PlayWaveEvent clipEvent) | if (rawClipEvent is not PlayWaveEvent clipEvent) | ||
Line 71: | Line 82: | ||
} | } | ||
− | foreach ( | + | foreach (PlayWaveVariant variant in clipEvent.GetVariants()) |
{ | { | ||
− | + | hasVariants = true; | |
− | + | ||
+ | using SoundEffect effect = variant.GetSoundEffect(); | ||
+ | |||
+ | yield return new SoundInfo | ||
+ | { | ||
+ | Category = category, | ||
+ | Name = name, | ||
+ | Id = variant.track | ||
+ | }; | ||
} | } | ||
} | } | ||
Line 80: | Line 99: | ||
} | } | ||
− | // | + | // invalid sound, should never happen |
− | if ( | + | if (!hasVariants) |
− | + | this.Monitor.Log($"Complex sound '{name}' unexpectedly has no variants.", LogLevel.Error); | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
} | } | ||
} | } | ||
Line 130: | Line 130: | ||
} | } | ||
</syntaxhighlight>}} | </syntaxhighlight>}} | ||
− | —<small>[[User:Pathoschild|Pathoschild]] ([[User talk:Pathoschild|talk]]) | + | |
+ | The game data only has the numerical category IDs, but I confirmed the category names with one of the game developers: | ||
+ | |||
+ | {| class="wikitable" | ||
+ | |- | ||
+ | ! id | ||
+ | ! name | ||
+ | ! my notes | ||
+ | |- | ||
+ | | 1 | ||
+ | | <tt>Default</tt> | ||
+ | | seems to be unused. | ||
+ | |- | ||
+ | | 2 | ||
+ | | <tt>Music</tt> | ||
+ | | | ||
+ | |- | ||
+ | | 3 | ||
+ | | <tt>Sound</tt> | ||
+ | | | ||
+ | |- | ||
+ | | 4 | ||
+ | | <tt>Ambient</tt> | ||
+ | | listed on the wiki as "Music (ambient)" just to group the music tracks. | ||
+ | |- | ||
+ | | 5 | ||
+ | | <tt>Footsteps</tt> | ||
+ | | | ||
+ | |} | ||
+ | |||
+ | —<small>[[User:Pathoschild|Pathoschild]] ([[User talk:Pathoschild|talk]]) 04:30, 17 October 2021 (UTC)</small> |
Revision as of 04:30, 17 October 2021
Track list export
The initial track list was exported with this quick 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.Category).OrderBy(p => p.Key))
{
output.AppendLine($"==={categoryGroup.Key}===");
output.AppendLine("{| class=\"wikitable sortable\"");
output.AppendLine("|-\n! name\n! soundbank ID\n! description");
foreach (SoundInfo sound in categoryGroup.OrderBy(p => p.Name).ThenBy(p => p.Id))
{
string soundIdLabel = sound.Id.ToString("X").ToLower().PadLeft(8, '0');
output.AppendLine($"|-\n| <tt>{sound.Name}</tt>\n| data-sort-value=\"{sound.Id}\"| <tt>{soundIdLabel}</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<SoundInfo> GetTracks()
{
SoundBank soundBank = this.Helper.Reflection.GetField<SoundBank>(Game1.soundBank, "soundBank").GetValue();
Dictionary<string, CueDefinition> cues = this.Helper.Reflection.GetField<Dictionary<string, CueDefinition>>(soundBank, "_cues").GetValue();
foreach (var entry in cues)
{
foreach (XactSoundBankSound sound in entry.Value.sounds)
{
string name = entry.Key;
string category = this.GetCategoryName(sound.categoryID);
// simple sound
if (!sound.complexSound)
{
using SoundEffectInstance instance = sound.GetSimpleSoundInstance();
yield return new SoundInfo
{
Category = category,
Name = name,
Id = 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;
using SoundEffect effect = variant.GetSoundEffect();
yield return new SoundInfo
{
Category = category,
Name = name,
Id = variant.track
};
}
}
}
}
// invalid sound, should never happen
if (!hasVariants)
this.Monitor.Log($"Complex sound '{name}' unexpectedly has no variants.", LogLevel.Error);
}
}
}
/// <summary>Get a human-readable name for a raw audio category ID.</summary>
/// <param name="categoryId">The raw category ID.</param>
private string GetCategoryName(uint categoryId)
{
// the categories seem to be arbitrarily defined for Stardew Valley;
// these are approximate labels based on the tracks in each group.
return categoryId switch
{
2 => "Music",
3 => "Sound",
4 => "Music (ambient)",
5 => "Footsteps",
_ => categoryId.ToString()
};
}
}
public class SoundInfo
{
public string Category { get; set; }
public string Name { get; set; }
public int Id { get; set; }
}
|
The game data only has the numerical category IDs, but I confirmed the 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) 04:30, 17 October 2021 (UTC)