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)}");
}
}
}