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