using System; using System.Collections.Generic; using System.IO; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Graphics; using NAudio.Vorbis; using NAudio.Wave; using StardewModdingAPI; using StardewValley; namespace SimpleSoundManager.Framework { /// A content pack which can provide music and sounds. public class MusicPack { /********* ** Fields *********/ /// The name of the folder which contains the saved player settings. private readonly string DataFolderName = "data"; /// The name of the folder which contains available music. private readonly string MusicFolderName = "music"; /********* ** Accessors *********/ /// The underlying content pack. public IContentPack ContentPack { get; } /// The current song name being played, if any. public string lastPlayedSoundName { get; private set; } /// The currently sound being played, if any. public SoundEffectInstance lastPlayedSound { get; private set; } public Dictionary> playingSounds; /// The manifest info. public IManifest Manifest => this.ContentPack.Manifest; /// The name of the music pack. public string Name => this.ContentPack.Manifest.Name; /// The available sounds. public Dictionary Sounds { get; } = new Dictionary(); /********* ** Public methods *********/ /// Construct an instance. /// The underlying content pack. public MusicPack(IContentPack contentPack) { this.ContentPack = contentPack; this.playingSounds = new Dictionary>(); this.LoadMusicFiles(); } /// Play a song. /// The song name to play. public void PlaySound(string name) { // get sound if (!this.Sounds.TryGetValue(name, out SoundEffectInstance sound)) { ModCore.ModMonitor.Log("An error occured where we can't find the song anymore. Weird. Please contact Omegasis with a SMAPI Log and describe when/how the event occured."); return; } // play sound this.lastPlayedSoundName = name; this.lastPlayedSound = sound; this.lastPlayedSound.Play(); if (this.playingSounds.ContainsKey(name)) { this.playingSounds[name].Add(sound); } else { this.playingSounds.Add(name,new List()); this.playingSounds[name].Add(sound); } } /// Stop the currently playing song. public void StopSound() { this.lastPlayedSound?.Stop(true); this.lastPlayedSoundName = null; } /// /// Stops all of the currently playing sounds. /// public void stopAllSounds() { foreach (string soundName in playingSounds.Keys) { for (int i = 0; i < playingSounds[soundName].Count; i++) { if (playingSounds[soundName][i].State == SoundState.Stopped) { playingSounds[soundName][i].Stop(true); continue; } } } } /// /// Updates the music pack to clean up all sounds that have been stopped. /// public void update() { ///Clean up the list. foreach(string soundName in playingSounds.Keys) { for(int i = 0; i < playingSounds[soundName].Count; i++) { if(playingSounds[soundName][i].State== SoundState.Stopped) { playingSounds[soundName].RemoveAt(i); i--; continue; } } } } /// Get whether the content pack is currently playing a sound. public bool IsPlaying() { return this.lastPlayedSound?.State == SoundState.Playing; } /// /// Checks if there is a sound with said name playing. /// /// /// public bool IsPlaying(string SoundName) { return this.playingSounds[SoundName].FindAll(s => s.State== SoundState.Playing).Count>0; } /********* ** Private methods *********/ /// Load in the music files from the pack's respective Directory/Songs folder. Typically Content/Music/Wav/FolderName/Songs private void LoadMusicFiles() { DateTime startTime = DateTime.Now; DirectoryInfo songFolder = new DirectoryInfo(Path.Combine(this.ContentPack.DirectoryPath, this.MusicFolderName)); foreach (FileInfo file in songFolder.GetFiles()) { // get name string name = Path.GetFileNameWithoutExtension(file.Name); if (this.Sounds.ContainsKey(name)) continue; // load data SoundEffect effect = null; using (Stream waveFileStream = File.OpenRead(file.FullName)) { switch (file.Extension) { case ".wav": effect = SoundEffect.FromStream(waveFileStream); break; case ".mp3": using (Mp3FileReader reader = new Mp3FileReader(waveFileStream)) using (WaveStream pcmStream = WaveFormatConversionStream.CreatePcmStream(reader)) { string tempPath = Path.Combine(songFolder.FullName, $"{name}.wav"); ModCore.ModMonitor.Log($"Converting: {tempPath}"); WaveFileWriter.CreateWaveFile(tempPath, pcmStream); using (Stream tempStream = File.OpenRead(tempPath)) effect = SoundEffect.FromStream(tempStream); File.Delete(tempPath); } break; case ".ogg": // Credits: https://social.msdn.microsoft.com/Forums/vstudio/en-US/100a97af-2a1c-4b28-b464-d43611b9b5d6/converting-multichannel-ogg-to-stereo-wav-file?forum=csharpgeneral using (VorbisWaveReader vorbisStream = new VorbisWaveReader(file.FullName)) { string tempPath = Path.Combine(songFolder.FullName, $"{name}.wav"); ModCore.DebugLog($"Converting: {tempPath}"); WaveFileWriter.CreateWaveFile(tempPath, vorbisStream.ToWaveProvider16()); using (Stream tempStream = File.OpenRead(tempPath)) effect = SoundEffect.FromStream(tempStream); File.Delete(tempPath); } break; default: ModCore.ModMonitor.Log($"Unsupported file extension {file.Extension}.", LogLevel.Warn); break; } } if (effect == null) continue; // add sound SoundEffectInstance instance = effect.CreateInstance(); this.Sounds.Add(name, instance); } // log loading time if (ModCore.Config.EnableDebugLog) ModCore.ModMonitor.Log($"Time to load WAV music pack {this.Name}: {startTime.Subtract(DateTime.Now)}"); } } }