Line 1: |
Line 1: |
| ==Track list export== | | ==Track list export== |
− | The initial track list was exported with this quick code, which can be run as a [[Modding:Modder Guide/Get Started|C# mod]]: | + | The initial track list was exported with this code, which can be run as a [[Modding:Modder Guide/Get Started|C# mod]]: |
| {{collapse|source code|content=<syntaxhighlight lang="c#"> | | {{collapse|source code|content=<syntaxhighlight lang="c#"> |
| /// <summary>The main entry point for the mod.</summary> | | /// <summary>The main entry point for the mod.</summary> |
Line 15: |
Line 15: |
| StringBuilder output = new(); | | StringBuilder output = new(); |
| | | |
− | foreach (var categoryGroup in this.GetTracks().GroupBy(p => p.Category).OrderBy(p => p.Key)) | + | foreach (var categoryGroup in this.GetTracks().GroupBy(p => p.GetCategoryName()).OrderBy(p => p.Key)) |
| { | | { |
| output.AppendLine($"==={categoryGroup.Key}==="); | | output.AppendLine($"==={categoryGroup.Key}==="); |
| output.AppendLine("{| class=\"wikitable sortable\""); | | output.AppendLine("{| class=\"wikitable sortable\""); |
− | output.AppendLine("|-\n! name\n! soundbank ID\n! description"); | + | output.AppendLine("|-\n! name\n! wavebank\n! soundbank ID\n! description"); |
| | | |
− | foreach (SoundInfo sound in categoryGroup.OrderBy(p => p.Name).ThenBy(p => p.Id)) | + | 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| "); |
− | 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("|}"); |
Line 42: |
Line 38: |
| *********/ | | *********/ |
| /// <summary>Extract the music/sound tracks from the game's soundbank.</summary> | | /// <summary>Extract the music/sound tracks from the game's soundbank.</summary> |
− | private IEnumerable<SoundInfo> GetTracks() | + | private IEnumerable<TrackInfo> GetTracks() |
| { | | { |
| SoundBank soundBank = this.Helper.Reflection.GetField<SoundBank>(Game1.soundBank, "soundBank").GetValue(); | | 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(); | + | IEnumerable<CueDefinition> cues = this.Helper.Reflection.GetField<Dictionary<string, CueDefinition>>(soundBank, "_cues").GetValue().Values; |
| | | |
− | foreach (var entry in cues) | + | foreach (CueDefinition cue in cues) |
| { | | { |
− | foreach (XactSoundBankSound sound in entry.Value.sounds) | + | foreach (XactSoundBankSound sound in cue.sounds) |
| { | | { |
− | string name = entry.Key;
| |
− | string category = this.GetCategoryName(sound.categoryID);
| |
− |
| |
| // simple sound | | // simple sound |
| if (!sound.complexSound) | | if (!sound.complexSound) |
| { | | { |
− | using SoundEffectInstance instance = sound.GetSimpleSoundInstance();
| + | yield return new TrackInfo( |
− | | + | WavebankIndex: sound.waveBankIndex, |
− | yield return new SoundInfo | + | CategoryId: sound.categoryID, |
− | {
| + | Name: cue.name, |
− | Category = category, | + | Index: sound.trackIndex |
− | Name = name, | + | ); |
− | Id = sound.trackIndex | |
− | }; | |
| continue; | | continue; |
| } | | } |
Line 86: |
Line 77: |
| hasVariants = true; | | hasVariants = true; |
| | | |
− | using SoundEffect effect = variant.GetSoundEffect();
| + | yield return new TrackInfo( |
− | | + | WavebankIndex: variant.waveBank, |
− | yield return new SoundInfo | + | CategoryId: sound.categoryID, |
− | {
| + | Name: cue.name, |
− | Category = category, | + | Index: variant.track |
− | Name = name, | + | ); |
− | Id = variant.track | |
− | }; | |
| } | | } |
| } | | } |
Line 101: |
Line 90: |
| // invalid sound, should never happen | | // invalid sound, should never happen |
| if (!hasVariants) | | if (!hasVariants) |
− | this.Monitor.Log($"Complex sound '{name}' unexpectedly has no variants.", LogLevel.Error); | + | this.Monitor.Log($"Complex sound '{cue.name}' unexpectedly has no variants.", LogLevel.Error); |
| } | | } |
| } | | } |
| } | | } |
| + | } |
| | | |
− | /// <summary>Get a human-readable name for a raw audio category ID.</summary>
| + | /// <summary>A sound or music track in a wavebank.</summary> |
− | /// <param name="categoryId">The raw category ID.</param>
| + | /// <param name="WavebankIndex">The wavebank which contains the track.</param> |
− | private string GetCategoryName(uint categoryId) | + | /// <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() |
| { | | { |
− | // the categories seem to be arbitrarily defined for Stardew Valley; | + | return this.CategoryId switch |
− | // these are approximate labels based on the tracks in each group.
| |
− | return categoryId switch
| |
| { | | { |
| 2 => "Music", | | 2 => "Music", |
Line 118: |
Line 112: |
| 4 => "Music (ambient)", | | 4 => "Music (ambient)", |
| 5 => "Footsteps", | | 5 => "Footsteps", |
− | _ => categoryId.ToString() | + | _ => 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() |
| }; | | }; |
| } | | } |
− | }
| |
| | | |
− | public class SoundInfo
| + | /// <summary>Get the hexadecimal soundbank ID which matches the filenames exported by unxwb.</summary> |
− | {
| + | public string GetSoundbankId() |
− | public string Category { get; set; } | + | { |
− | public string Name { get; set; } | + | return this.Index |
− | public int Id { get; set; } | + | .ToString("X") |
| + | .ToLower() |
| + | .PadLeft(8, '0'); |
| + | } |
| } | | } |
| </syntaxhighlight>}} | | </syntaxhighlight>}} |
| | | |
− | The game data only has the numerical category IDs, but I confirmed the category names with one of the game developers: | + | The game data only has the numerical category IDs, but I confirmed the internal category names with one of the game developers: |
| | | |
| {| class="wikitable" | | {| class="wikitable" |
Line 160: |
Line 167: |
| |} | | |} |
| | | |
− | —<small>[[User:Pathoschild|Pathoschild]] ([[User talk:Pathoschild|talk]]) 04:30, 17 October 2021 (UTC)</small> | + | —<small>[[User:Pathoschild|Pathoschild]] ([[User talk:Pathoschild|talk]]) 05:21, 17 October 2021 (UTC)</small> |