////////////////////////////////////////////////////////////////////////////// /// /// WaveStream processor class for manipulating audio stream in C# with /// SoundTouch library. /// /// This module uses NAudio library for C# audio file input / output /// /// Author : Copyright (c) Olli Parviainen /// Author e-mail : oparviai 'at' iki.fi /// SoundTouch WWW: http://www.surina.net/soundtouch /// //////////////////////////////////////////////////////////////////////////////// // // License for this source code file: Microsoft Public License(Ms-PL) // //////////////////////////////////////////////////////////////////////////////// using NAudio.Wave; using soundtouch; using System; namespace csharp_example { /// /// Helper class that allow writing status texts to the host application /// public class StatusMessage { /// /// Handler for status message events. Subscribe this from the host application /// public static event EventHandler statusEvent; /// /// Pass a status message to the host application /// public static void Write(string msg) { if (statusEvent != null) { statusEvent(null, msg); } } } /// /// NAudui WaveStream class for processing audio stream with SoundTouch effects /// public class WaveStreamProcessor : WaveStream { private WaveChannel32 inputStr; public SoundTouch st; private byte[] bytebuffer = new byte[4096]; private float[] floatbuffer = new float[1024]; bool endReached = false; /// /// Constructor /// /// WaveChannel32 stream used for processor stream input public WaveStreamProcessor(WaveChannel32 input) { inputStr = input; st = new SoundTouch(); st.Channels = (uint)input.WaveFormat.Channels; st.SampleRate = (uint)input.WaveFormat.SampleRate; } /// /// True if end of stream reached /// public bool EndReached { get { return endReached; } } public override long Length { get { return inputStr.Length; } } public override long Position { get { return inputStr.Position; } set { inputStr.Position = value; } } public override WaveFormat WaveFormat { get { return inputStr.WaveFormat; } } /// /// Overridden Read function that returns samples processed with SoundTouch. Returns data in same format as /// WaveChannel32 i.e. stereo float samples. /// /// Buffer where to return sample data /// Offset from beginning of the buffer /// Number of bytes to return /// Number of bytes copied to buffer public override int Read(byte[] buffer, int offset, int count) { try { // Iterate until enough samples available for output: // - read samples from input stream // - put samples to SoundStretch processor while (st.AvailableSampleCount < count) { int nbytes = inputStr.Read(bytebuffer, 0, bytebuffer.Length); if (nbytes == 0) { // end of stream. flush final samples from SoundTouch buffers to output if (endReached == false) { endReached = true; // do only once to avoid continuous flushing st.Flush(); } break; } // binary copy data from "byte[]" to "float[]" buffer Buffer.BlockCopy(bytebuffer, 0, floatbuffer, 0, nbytes); st.PutSamples(floatbuffer, (uint)(nbytes / 8)); } // ensure that buffer is large enough to receive desired amount of data out if (floatbuffer.Length < count / 4) { floatbuffer = new float[count / 4]; } // get processed output samples from SoundTouch int numsamples = (int)st.ReceiveSamples(floatbuffer, (uint)(count / 8)); // binary copy data from "float[]" to "byte[]" buffer Buffer.BlockCopy(floatbuffer, 0, buffer, offset, numsamples * 8); return numsamples * 8; // number of bytes } catch (Exception exp) { StatusMessage.Write("exception in WaveStreamProcessor.Read: " + exp.Message); return 0; } } /// /// Clear the internal processor buffers. Call this if seeking or rewinding to new position within the stream. /// public void Clear() { st.Clear(); endReached = false; } } /// /// Class that opens & plays MP3 file and allows real-time audio processing with SoundTouch /// while playing /// public class SoundProcessor { Mp3FileReader mp3File; WaveOut waveOut; public WaveStreamProcessor streamProcessor; /// /// Start / resume playback /// /// true if successful, false if audio file not open public bool Play() { if (waveOut == null) return false; if (waveOut.PlaybackState != PlaybackState.Playing) { waveOut.Play(); } return true; } /// /// Pause playback /// /// true if successful, false if audio not playing public bool Pause() { if (waveOut == null) return false; if (waveOut.PlaybackState == PlaybackState.Playing) { waveOut.Stop(); return true; } return false; } /// /// Stop playback /// /// true if successful, false if audio file not open public bool Stop() { if (waveOut == null) return false; waveOut.Stop(); mp3File.Position = 0; streamProcessor.Clear(); return true; } /// /// Event for "playback stopped" event. 'bool' argument is true if playback has reached end of stream. /// public event EventHandler PlaybackStopped; /// /// Proxy event handler for receiving playback stopped event from WaveOut /// protected void EventHandler_stopped(object sender, StoppedEventArgs args) { bool isEnd = streamProcessor.EndReached; if (isEnd) { Stop(); } if (PlaybackStopped != null) { PlaybackStopped(sender, isEnd); } } /// /// Open MP3 file /// /// Path to file to open /// true if successful public bool OpenMp3File(string filePath) { try { mp3File = new Mp3FileReader(filePath); WaveChannel32 inputStream = new WaveChannel32(mp3File); inputStream.PadWithZeroes = false; // don't pad, otherwise the stream never ends streamProcessor = new WaveStreamProcessor(inputStream); waveOut = new WaveOut() { DesiredLatency = 100 }; waveOut.Init(streamProcessor); // inputStream); waveOut.PlaybackStopped += EventHandler_stopped; StatusMessage.Write("Opened file " + filePath); return true; } catch (Exception exp) { // Error in opening file waveOut = null; StatusMessage.Write("Can't open file: " + exp.Message); return false; } } } }