This project has moved. For the latest updates, please go here.

Sections

1. Introduction / The Sound Player

2. Sound Player Basic Requirements (Property Change Notification)

3. Basic Audio Playback

4. Spectrum Analyzer Support

5. Stereo Waveform Timeline Support

 

1. Introduction / The Sound Player

The WPFSVL makes a certain assumption about your application’s architecture. It assumes you’ll have some object, somewhere, that is in charge of monitoring audio playback. It also assumes you’ll be able to extend or wrap this object so that it can implement certain interfaces that the WPFSVL can make use of. Since .NET is object-oriented, this is probably how you’re already controlling your audio playback, so it shouldn’t be much of a stretch. With regard to BASS.NET, most users end up wrapping BASS.NET’s calls into some more object-oriented structure. In order to use WPFSVL, you’ll need to do the same.

All of the logic mentioned in this guide can be found in the samples included with the source code. I’ll do my best to keep this guide up to date, but I encourage you all to take a close look at that source code for the most up-to-date versions of SoundPlayer implementation for BASS.NET.

It should also be noted that not ALL of the controls in the WPFSVL need a sound player object. Only the ones that have a method named RegisterSoundPlayer() will require a sound player object.

 

2. Sound Player Basic Requirements (Property Change Notification)

All of the controls that need a Sound Player object will also require two things. 1.) The sound player must implement INotifyPropertyChanged so the controls can be notified when something changes. 2.) The sound player must have an IsPlaying property so the controls can know when sound is playing. Internally, IsPlaying is used to stop animation timers and rendering loops when sound isn’t playing. Without it, some controls like the Spectrum Analyzer would cause the application to use a fair amount of processing power even while your sound application idles and does nothing. Putting that together, you’ll end up a class scaffolding that looks like the code below.

1 public class BassEngine : ISoundPlayer 2 { 3 private bool isPlaying; 4 5 #region INotifyPropertyChanged 6 public event PropertyChangedEventHandler PropertyChanged; 7 8 private void NotifyPropertyChanged(String info) 9 { 10 if (PropertyChanged != null) 11 { 12 PropertyChanged(this, new PropertyChangedEventArgs(info)); 13 } 14 } 15 #endregion 16 17 public bool IsPlaying 18 { 19 get { return isPlaying; } 20 protected set 21 { 22 bool oldValue = isPlaying; 23 isPlaying = value; 24 if (oldValue != isPlaying) 25 NotifyPropertyChanged("IsPlaying"); 26 positionTimer.IsEnabled = value; 27 } 28 } 29 }

An alternative way you may consider implementing INotifyPropertyChanged is to make your sound player a DependencyObject and implement the required properties as DependencyProperties. I chose *not* to do that here because many of you will be implementing your sound player in an assembly that is separate from your presentation code and references.  However, if your sound player is in the same assembly as your UI, the DependencyObject route may suit you well.

 

3. Basic Audio Playback

Chances are, if you’re here, you probably already have some sort of sound playback in your application and you’re just looking for ways to include some neat visualizations. My hope is that you won’t need to change anything too drastically to make it work with the WPFSVL controls. That said, if you don’t already have a sound playback engine, are interested in seeing how I personally implemented one, or want some more information on why I implemented things the ways I did then this is the section for you. Again, remember this is only my personal implementation of a sound player and you’re free to implement a sound player however you would like.

The first thing I know about my sound player is that I only need one of them. That is to say, I’m not going to be playing multiple files at the same time so I only need to be processing one sound stream at time. Only one stream means only one sound player. As such, I’m going to use the Singleton Pattern to ensure that only one instance of my sound player is ever instantiated. We’ll build on my code from section 2 with the code below.

1 public class BassEngine : ISoundPlayer 2 { 3 ... Other code ... 4 5 private static BassEngine instance; 6 7 public static BassEngine Instance 8 { 9 get 10 { 11 if (instance == null) 12 instance = new BassEngine(); 13 return instance; 14 } 15 } 16 }

Continuing on, our goal with the basic sound playback is twofold.

  1. We need to provide the basic file opening structure so we can open a file and start playing it.
  2. We need to provide the right kind of public properties and methods from which the UI can control the playback.

Thus, take the code below and add it to the code you already have in the BassEngine class. The normal flow would have the user calling OpenFile() and passing it a path, presumably through a “File > Open” dialog or something similar. Once passing it a valid path, we’ll mark CanPlay as true. At this point, the user could call Play() (through something like a Play button). Pressing Play would, in turn, make the Stop() and Pause() commands available through the CanStop and CanPause properties respectively. Since CanStop and CanPause will raise property changed notification, it is a great idea to bind button’s IsEnabled properties in your UI to these properties.

1 public class BassEngine : ISoundPlayer 2 { 3 private int activeStreamHandle; 4 private bool canPlay; 5 private bool canPause; 6 private bool canStop; 7 8 ... Other code ... 9 10 public void Stop() 11 { 12 if (ActiveStreamHandle != 0) 13 { 14 Bass.BASS_ChannelStop(ActiveStreamHandle); 15 Bass.BASS_ChannelSetPosition(ActiveStreamHandle, ChannelPosition); 16 } 17 IsPlaying = false; 18 CanStop = false; 19 CanPlay = true; 20 CanPause = false; 21 } 22 23 public void Pause() 24 { 25 if (IsPlaying && CanPause) 26 { 27 Bass.BASS_ChannelPause(ActiveStreamHandle); 28 IsPlaying = false; 29 CanPlay = true; 30 CanPause = false; 31 } 32 } 33 34 public void Play() 35 { 36 if (CanPlay) 37 { 38 PlayCurrentStream(); 39 IsPlaying = true; 40 CanPause = true; 41 CanPlay = false; 42 CanStop = true; 43 } 44 } 45 46 public bool OpenFile(string path) 47 { 48 Stop(); 49 50 if (ActiveStreamHandle != 0) 51 { 52 Bass.BASS_StreamFree(ActiveStreamHandle); 53 } 54 55 if (System.IO.File.Exists(path)) 56 { 57 // Create Stream 58   ActiveStreamHandle = Bass.BASS_StreamCreateFile(path, 0, 0, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN); 59 if (ActiveStreamHandle != 0) 60 { 61 CanPlay = true; 62 return true; 63 } 64 else 65 { 66 ActiveStreamHandle = 0; 67 CanPlay = false; 68 } 69 } 70 return false; 71 } 72 73 private void PlayCurrentStream() 74 { 75 // Play Stream 76   if (ActiveStreamHandle != 0) 77 { 78 Bass.BASS_ChannelPlay(ActiveStreamHandle, false); 79 } 80 } 81 82 public int ActiveStreamHandle 83 { 84 get { return activeStreamHandle; } 85 protected set 86 { 87 int oldValue = activeStreamHandle; 88 activeStreamHandle = value; 89 if (oldValue != activeStreamHandle) 90 NotifyPropertyChanged("ActiveStreamHandle"); 91 } 92 } 93 94 public bool CanPlay 95 { 96 get { return canPlay; } 97 protected set 98 { 99 bool oldValue = canPlay; 100 canPlay = value; 101 if (oldValue != canPlay) 102 NotifyPropertyChanged("CanPlay"); 103 } 104 } 105 106 public bool CanPause 107 { 108 get { return canPause; } 109 protected set 110 { 111 bool oldValue = canPause; 112 canPause = value; 113 if (oldValue != canPause) 114 NotifyPropertyChanged("CanPause"); 115 } 116 } 117 118 public bool CanStop 119 { 120 get { return canStop; } 121 protected set 122 { 123 bool oldValue = canStop; 124 canStop = value; 125 if (oldValue != canStop) 126 NotifyPropertyChanged("CanStop"); 127 } 128 } 129 }

4. Spectrum Analyzer Support

If you’ve done everything above, we’re very close to having support for the Spectrum Analyzer. The spectrum analyzer requires a few things of the sound player.

  1. A mechanism to get FFT data that has been translated to real intensity values (i.e., it has already calculated out the imaginary values).
  2. A way to know which value in the FFT array corresponds with a particular frequency.
  3. The sound player must be marked indicating it has support for the Spectrum Analyzer. This is done by inheriting the ISpectrumPlayer interface.

Luckily, BASS.NET makes the calculations in the first two requirements fairly trivial. We will, however, need to provide some information to BASS. First, we need to know the sample rate of the file we’re listening to. This is because the maximum frequency of the song will be half of the sample rate. In other words, a file with a sample rate of 44.1 kHz will have a maximum frequency of 22.05kHz. To obtain the sample rate, we’ll add a little bit of code to the OpenFile() method we wrote above. Unlike many other libraries, the rest of the sampling logic is taken care of for us, as is the FFT formula.  Add the following code:

1 public class BassEngine : ISpectrumPlayer 2 { 3 private readonly int fftDataSize = (int)FFTDataSize.FFT2048; 4 private readonly int maxFFT = (int)(BASSData.BASS_DATA_AVAILABLE | BASSData.BASS_DATA_FFT2048); 5 private int sampleFrequency = 44100; 6 7 ... Other code ... 8 9 public bool OpenFile(string path) 10 { 11 Stop(); 12 13 if (ActiveStreamHandle != 0) 14 { 15 Bass.BASS_StreamFree(ActiveStreamHandle); 16 } 17 18 if (System.IO.File.Exists(path)) 19 { 20 // Create Stream 21   ActiveStreamHandle = Bass.BASS_StreamCreateFile(path, 0, 0, BASSFlag.BASS_SAMPLE_FLOAT | BASSFlag.BASS_STREAM_PRESCAN); 22 if (ActiveStreamHandle != 0) 23 { 24 // Obtain the sample rate of the stream 25   BASS_CHANNELINFO info = new BASS_CHANNELINFO(); 26 Bass.BASS_ChannelGetInfo(ActiveStreamHandle, info); 27 sampleFrequency = info.freq; 28 29 CanPlay = true; 30 return true; 31 } 32 else 33 { 34 ActiveStreamHandle = 0; 35 CanPlay = false; 36 } 37 } 38 return false; 39 } 40 41 public int GetFFTFrequencyIndex(int frequency) 42 { 43 return Utils.FFTFrequency2Index(frequency, fftDataSize, sampleFrequency); 44 } 45 46 public bool GetFFTData(float[] fftDataBuffer) 47 { 48 return (Bass.BASS_ChannelGetData(ActiveStreamHandle, fftDataBuffer, maxFFT)) > 0; 49 } 50 }

We’ve added the two methods required by ISpectrumPlayer, GetFFTFrequencyIndex() and GetFFTData(). Now we should be able to add a Spectrum Analyzer to our Window and register the BassEngine class as the Spectrum Analyzer’s sound player. Start with adding the actual Spectrum Analyzer control somewhere in your Window’s XAML. The sample below gives an example of referencing the WPFSVL namespace.

 

1 <Window xmlns:svl="clr-namespace:WPFSoundVisualizationLib;assembly=WPFSoundVisualizationLib""> 2 3 <!-- Rest of your Windows code --> 4 5 <svl:SpectrumAnalyzer x:Name="spectrumAnalyzer" /> 6 7  </Window>

Now, in the code-behind, we’ll want to register our sound player with the Spectrum Analyzer control:

1 public MainWindow() 2 { 3 InitializeComponent(); 4 5 spectrumAnalyzer.RegisterSoundPlayer(BassEngine.Instance); 6 }

We should now have a working Spectrum Analyzer! Be sure to check the samples included with the source code to see a more complete example with a few extra features!

5. Stereo Waveform Timeline Support

Coming soon...

Last edited Apr 14, 2011 at 4:46 AM by jacobj, version 4

Comments

turkush Feb 19, 2013 at 2:17 PM 
Hi,
Im using NAudioEngine for playing audio files. but im unable to play list of audios in loop. would it be possible with nAudio?

turkush Feb 13, 2013 at 2:48 PM 
Spectrum Analyzer with NAudio & WPFSoundVisualizationLib Stop is working Like Pause, and Pause is not working- when play after Pause the voice gets distorted.

pieperu Oct 9, 2012 at 8:35 PM 
A slight oversight on my part, the latest samples code can be downloaded here :)

http://wpfsvl.codeplex.com/SourceControl/changeset/view/16372#18164

pieperu Oct 9, 2012 at 3:05 PM 
Hi

Would just like to say that the work you have done here is excellent. Truly impressive.

I am in the process of integrating your controls into an app i wrote to simplify my music library. I'm currently working on getting the Waveform to be displayed. If you have any documentation on this, or if you have a guest login for your TFS to see your code that would be such a huge help because I cant seem to open the samples.

Thanks again for your work, alot of time and effort must have gone into this!

Pietro

han0001 May 2, 2012 at 3:58 AM 
Hi,

I downloaded your sample and hoped to read it in VS1010. But the two integrated examples(Bass.net and NAdio) were in the tsf.codeplex.com/tsf/tsf27. I couldn't open them. So I can't learn the way you use your controls.

Regards
Jack Han

han0001 May 2, 2012 at 3:53 AM 
Hi,

Thank you for your work. We are wiating for your more guide, ie integrating with NAudio and getting your Waveform Timeline working with them.

regards!
Jack Han