1 //////////////////////////////////////////////////////////////////////////////
2 ///
3 /// WaveStream processor class for manipulating audio stream in C# with
4 /// SoundTouch library.
5 ///
6 /// This module uses NAudio library for C# audio file input / output
7 ///
8 /// Author        : Copyright (c) Olli Parviainen
9 /// Author e-mail : oparviai 'at' iki.fi
10 /// SoundTouch WWW: http://www.surina.net/soundtouch
11 ///
12 ////////////////////////////////////////////////////////////////////////////////
13 //
14 // License for this source code file: Microsoft Public License(Ms-PL)
15 //
16 ////////////////////////////////////////////////////////////////////////////////
17 
18 using NAudio.Wave;
19 using soundtouch;
20 using System;
21 
22 namespace csharp_example
23 {
24     /// <summary>
25     /// Helper class that allow writing status texts to the host application
26     /// </summary>
27     public class StatusMessage
28     {
29         /// <summary>
30         /// Handler for status message events. Subscribe this from the host application
31         /// </summary>
32         public static event EventHandler<string> statusEvent;
33 
34         /// <summary>
35         /// Pass a status message to the host application
36         /// </summary>
Write(string msg)37         public static void Write(string msg)
38         {
39             if (statusEvent != null)
40             {
41                 statusEvent(null, msg);
42             }
43         }
44     }
45 
46     /// <summary>
47     /// NAudui WaveStream class for processing audio stream with SoundTouch effects
48     /// </summary>
49     public class WaveStreamProcessor : WaveStream
50     {
51         private WaveChannel32 inputStr;
52         public SoundTouch st;
53 
54         private byte[] bytebuffer = new byte[4096];
55         private float[] floatbuffer = new float[1024];
56         bool endReached = false;
57 
58 
59         /// <summary>
60         /// Constructor
61         /// </summary>
62         /// <param name="input">WaveChannel32 stream used for processor stream input</param>
WaveStreamProcessor(WaveChannel32 input)63         public WaveStreamProcessor(WaveChannel32 input)
64         {
65             inputStr = input;
66             st = new SoundTouch();
67             st.Channels = (uint)input.WaveFormat.Channels;
68             st.SampleRate = (uint)input.WaveFormat.SampleRate;
69         }
70 
71         /// <summary>
72         /// True if end of stream reached
73         /// </summary>
74         public bool EndReached
75         {
76             get { return endReached; }
77         }
78 
79 
80         public override long Length
81         {
82             get
83             {
84                 return inputStr.Length;
85             }
86         }
87 
88 
89         public override long Position
90         {
91             get
92             {
93                 return inputStr.Position;
94             }
95 
96             set
97             {
98                 inputStr.Position = value;
99             }
100         }
101 
102 
103         public override WaveFormat WaveFormat
104         {
105             get
106             {
107                 return inputStr.WaveFormat;
108             }
109         }
110 
111         /// <summary>
112         /// Overridden Read function that returns samples processed with SoundTouch. Returns data in same format as
113         /// WaveChannel32 i.e. stereo float samples.
114         /// </summary>
115         /// <param name="buffer">Buffer where to return sample data</param>
116         /// <param name="offset">Offset from beginning of the buffer</param>
117         /// <param name="count">Number of bytes to return</param>
118         /// <returns>Number of bytes copied to buffer</returns>
Read(byte[] buffer, int offset, int count)119         public override int Read(byte[] buffer, int offset, int count)
120         {
121             try
122             {
123                 // Iterate until enough samples available for output:
124                 // - read samples from input stream
125                 // - put samples to SoundStretch processor
126                 while (st.AvailableSampleCount < count)
127                 {
128                     int nbytes = inputStr.Read(bytebuffer, 0, bytebuffer.Length);
129                     if (nbytes == 0)
130                     {
131                         // end of stream. flush final samples from SoundTouch buffers to output
132                         if (endReached == false)
133                         {
134                             endReached = true;  // do only once to avoid continuous flushing
135                             st.Flush();
136                         }
137                         break;
138                     }
139 
140                     // binary copy data from "byte[]" to "float[]" buffer
141                     Buffer.BlockCopy(bytebuffer, 0, floatbuffer, 0, nbytes);
142                     st.PutSamples(floatbuffer, (uint)(nbytes / 8));
143                 }
144 
145                 // ensure that buffer is large enough to receive desired amount of data out
146                 if (floatbuffer.Length < count / 4)
147                 {
148                     floatbuffer = new float[count / 4];
149                 }
150                 // get processed output samples from SoundTouch
151                 int numsamples = (int)st.ReceiveSamples(floatbuffer, (uint)(count / 8));
152                 // binary copy data from "float[]" to "byte[]" buffer
153                 Buffer.BlockCopy(floatbuffer, 0, buffer, offset, numsamples * 8);
154                 return numsamples * 8;  // number of bytes
155             }
156             catch (Exception exp)
157             {
158                 StatusMessage.Write("exception in WaveStreamProcessor.Read: " + exp.Message);
159                 return 0;
160             }
161         }
162 
163         /// <summary>
164         /// Clear the internal processor buffers. Call this if seeking or rewinding to new position within the stream.
165         /// </summary>
Clear()166         public void Clear()
167         {
168             st.Clear();
169             endReached = false;
170         }
171     }
172 
173 
174     /// <summary>
175     /// Class that opens & plays MP3 file and allows real-time audio processing with SoundTouch
176     /// while playing
177     /// </summary>
178     public class SoundProcessor
179     {
180         Mp3FileReader mp3File;
181         WaveOut waveOut;
182         public WaveStreamProcessor streamProcessor;
183 
184 
185         /// <summary>
186         /// Start / resume playback
187         /// </summary>
188         /// <returns>true if successful, false if audio file not open</returns>
Play()189         public bool Play()
190         {
191             if (waveOut == null) return false;
192 
193             if (waveOut.PlaybackState != PlaybackState.Playing)
194             {
195                 waveOut.Play();
196             }
197             return true;
198         }
199 
200 
201         /// <summary>
202         /// Pause playback
203         /// </summary>
204         /// <returns>true if successful, false if audio not playing</returns>
Pause()205         public bool Pause()
206         {
207             if (waveOut == null) return false;
208 
209             if (waveOut.PlaybackState == PlaybackState.Playing)
210             {
211                 waveOut.Stop();
212                 return true;
213             }
214             return false;
215         }
216 
217 
218         /// <summary>
219         /// Stop playback
220         /// </summary>
221         /// <returns>true if successful, false if audio file not open</returns>
Stop()222         public bool Stop()
223         {
224             if (waveOut == null) return false;
225 
226             waveOut.Stop();
227             mp3File.Position = 0;
228             streamProcessor.Clear();
229             return true;
230         }
231 
232 
233 
234         /// <summary>
235         /// Event for "playback stopped" event. 'bool' argument is true if playback has reached end of stream.
236         /// </summary>
237         public event EventHandler<bool> PlaybackStopped;
238 
239 
240         /// <summary>
241         /// Proxy event handler for receiving playback stopped event from WaveOut
242         /// </summary>
EventHandler_stopped(object sender, StoppedEventArgs args)243         protected void EventHandler_stopped(object sender, StoppedEventArgs args)
244         {
245             bool isEnd = streamProcessor.EndReached;
246             if (isEnd)
247             {
248                 Stop();
249             }
250             if (PlaybackStopped != null)
251             {
252                 PlaybackStopped(sender, isEnd);
253             }
254         }
255 
256 
257         /// <summary>
258         /// Open MP3 file
259         /// </summary>
260         /// <param name="filePath">Path to file to open</param>
261         /// <returns>true if successful</returns>
OpenMp3File(string filePath)262         public bool OpenMp3File(string filePath)
263         {
264             try
265             {
266                 mp3File = new Mp3FileReader(filePath);
267                 WaveChannel32 inputStream = new WaveChannel32(mp3File);
268                 inputStream.PadWithZeroes = false;  // don't pad, otherwise the stream never ends
269                 streamProcessor = new WaveStreamProcessor(inputStream);
270 
271                 waveOut = new WaveOut()
272                 {
273                     DesiredLatency = 100
274                 };
275 
276                 waveOut.Init(streamProcessor);  // inputStream);
277                 waveOut.PlaybackStopped += EventHandler_stopped;
278 
279                 StatusMessage.Write("Opened file " + filePath);
280                 return true;
281             }
282             catch (Exception exp)
283             {
284                 // Error in opening file
285                 waveOut = null;
286                 StatusMessage.Write("Can't open file: " + exp.Message);
287                 return false;
288             }
289 
290         }
291     }
292 }
293