Get seamless looping music, and cross-fade, in your MonoGame-PlayPlayMini game using NAudio.
$ dotnet add package BenMakesGames.PlayPlayMini.NAudioSeamlessy-looping music is important for many games, but MonoGame's built-in music player isn't able to consistently loop music - more often than not, it lags, adding a short, but noticeable delay before looping.
PlayPlayMini.NAudio allows you to use NAudio to play music, resolving this issue, and adding support for playing multiple songs at once, and cross-fading between songs!
Hey, Listen! PlayPlayMini.NAudio is in early release; the API may change dramatically even between minor version numbers.
🧚 Hey, listen! You can support my development of open-source software on Patreon
dotnet add package BenMakesGames.PlayPlayMini.NAudio.csproj file to automatically include all songs; change the path as needed, of course:
<ItemGroup>
<None Update="Content\Music\**\*.*">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
GameStateManagerBuilder, use new NAudioSongMeta(...) instead of new SongMeta(...). When using new NAudioSongMeta(...), you must specify the extension of the song.
new NAudioSongMeta("TitleTheme", "Content/Music/TitleTheme.mp3")..AddServices(...) call as follows:
.AddServices((s, c, serviceWatcher) => {
...
// WaveOutEvent is only supported by Windows; for other OSes, replace WaveOutEvent with alternatives from this thread: https://github.com/naudio/NAudio/issues/585
s.RegisterType<NAudioMusicPlayer<WaveOutEvent>>().As<INAudioMusicPlayer>()
.SingleInstance()
.OnActivating(audio => serviceWatcher.RegisterService(audio.Instance));
})
INAudioMusicPlayer, and wait for it to be .FullyLoaded, too.Hey, Listen! All songs you load must have the same sample rate (typically 44.1khz) and channel count (typically 2). When songs are loading, an error will be logged if they do not all match, and not all songs will be loaded.
.mp3, .aiff, and .wav files are supported out of the box. For other formats, you will need to install additional NAudio packages and do a little extra configuration:
.ogg) SupportAdd the NAudio.Vorbis package to your project.
In your game's .AddServices(...) configuration, add the following line:
s.RegisterInstance(new NAudioFileLoader("ogg", f => new VorbisWaveReader(f)));
.flac) SupportAdd the NAudio.Flac package to your project.
In your game's .AddServices(...) configuration, add the following line:
s.RegisterInstance(new NAudioFileLoader("flac", f => new FlacReader(f)));
In your game state or services, get an INAudioMusicPlayer via the constructor (just as you would any other service), and use it to play and stop songs.
Example:
// where musicPlayer is an instance of INAudioMusicPlayer:
musicPlayer.StopAllSongs(1000); // stop all songs, fading them out over 1 second
musicPlayer.PlaySong("TitleTheme", 0); // start the TitleTheme with no fade-in time
Refer to the reference, below, for a list of available methods.
INAudioMusicPlayer Method ReferenceHey, Listen! Negative fade-in and fade-out times are treated as 0.
PlayingSong PlaySong(string name, int fadeInMilliseconds = 0)Starts playing the specific song, fading it in over the specified number of milliseconds.
Songs which are already playing will not be stopped! You must explicitly stop them using StopAllSongs or StopSong (below).
If the song is already playing, it will not be played again (you cannot currently play two copies of the same song at the same time). If the song is fading in, its fade-in time will not be changed.
void StopAllSongs(int fadeOutMilliseconds = 0)Stops all songs, fading them out over the specified number of milliseconds.
Songs which are already fading out will not have their fade-out time changed. A fade-out time of 0 will always immediately stops all songs, however.
void StopAllSongsExcept(string[] songsToContinue, int fadeOutMilliseconds = 0)Works like StopAllSongs (above), but does NOT stop the songs named in songsToContinue.
void StopAllSongsExcept(string name, int fadeOutMilliseconds = 0)Works like StopAllSongs (above), but does NOT stop the named song.
void StopSong(string name, int fadeOutMilliseconds = 0)Like StopAllSongs (above), but stops only the named song.
void SetVolume(float volume)Changes the volume for all songs.
bool IsPlaying(string name)Returns true if the specific song is currently playing.
IEnumerable<PlayingSong> GetPlayingSongs()Returns an array of the names of all songs currently playing.