Modding talk:Audio: Difference between revisions
Appearance
imported>Pathoschild →Track list export: update code |
imported>Pathoschild →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)