Difference between revisions of "Modding talk:Audio"

From Stardew Valley Wiki
Jump to navigation Jump to search
(→‎Track list export: update code)
(→‎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)
 
             {
 
             {
                HashSet<int> ids = new();
 
 
                // from main sound info
 
 
                 string name = entry.Key;
 
                 string name = entry.Key;
 
                 string category = this.GetCategoryName(sound.categoryID);
 
                 string category = this.GetCategoryName(sound.categoryID);
                if (sound.trackIndex != 0)
 
                    ids.Add(sound.trackIndex);
 
  
                 // from variants
+
                 // 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 (var rawClipEvent in clip.clipEvents)
+
                         foreach (ClipEvent rawClipEvent in clip.clipEvents)
 
                         {
 
                         {
 
                             if (rawClipEvent is not PlayWaveEvent clipEvent)
 
                             if (rawClipEvent is not PlayWaveEvent clipEvent)
Line 71: Line 82:
 
                             }
 
                             }
  
                             foreach (var variant in clipEvent.GetVariants())
+
                             foreach (PlayWaveVariant variant in clipEvent.GetVariants())
 
                             {
 
                             {
                                 if (variant.track != 0)
+
                                 hasVariants = true;
                                     ids.Add(variant.track);
+
 
 +
                                using SoundEffect effect = variant.GetSoundEffect();
 +
 
 +
                                yield return new SoundInfo
 +
                                {
 +
                                    Category = category,
 +
                                    Name = name,
 +
                                     Id = variant.track
 +
                                };
 
                             }
 
                             }
 
                         }
 
                         }
Line 80: Line 99:
 
                 }
 
                 }
  
                 // export tracks
+
                 // invalid sound, should never happen
                 if (ids.Any())
+
                 if (!hasVariants)
                {
+
                     this.Monitor.Log($"Complex sound '{name}' unexpectedly has no variants.", LogLevel.Error);
                     foreach (int id in ids)
 
                    {
 
                        yield return new SoundInfo
 
                        {
 
                            Category = category,
 
                            Name = name,
 
                            Id = id
 
                        };
 
                    }
 
                }
 
                else
 
                {
 
                    yield return new SoundInfo
 
                    {
 
                        Category = category,
 
                        Name = name,
 
                        Id = 0
 
                    };
 
                }
 
 
             }
 
             }
 
         }
 
         }
Line 130: Line 130:
 
}
 
}
 
</syntaxhighlight>}}
 
</syntaxhighlight>}}
—<small>[[User:Pathoschild|Pathoschild]] ([[User talk:Pathoschild|talk]]) 03:22, 17 October 2021 (UTC)</small>
+
 
 +
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)