public static Info AnalyzeFile(string bpcmFile) { using (FileStream s = new FileStream(bpcmFile, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576, FileOptions.RandomAccess)) { BitstreamReader BPCM = new BitstreamReader(s); double duration = (double)BPCM.Analysis.DurationSampleCount / BPCM.Analysis.FrameSet[0].SamplingRate; TimeSpan dur = TimeSpan.FromSeconds(duration); string strDuration = String.Format("{0:00}d {1:00}h {2:00}m {3:00}s {4:000.000}ms", dur.Days, dur.Hours, dur.Minutes, dur.Seconds, (duration - Math.Floor(duration)) * 1000); return(new Info() { NumberOfChannels = BPCM.Analysis.FrameSet[0].Channels , SamplingRate = BPCM.Analysis.FrameSet[0].SamplingRate , Duration = BPCM.Analysis.Duration , DurationSampleCount = BPCM.Analysis.DurationSampleCount , DurationString = strDuration , BitrateMin = BPCM.Analysis.BitrateMinimum , BitrateAvg = BPCM.Analysis.BitrateAverage , BitrateMax = BPCM.Analysis.BitrateMaximum , BlockSizeNominal = BPCM.Analysis.BlockSizeNominal , BlockSizeAverage = BPCM.Analysis.BlockSizeAverage , BlockSizeMaximum = BPCM.Analysis.BlockSizeMaximum , BlockSizeMinimum = BPCM.Analysis.BlockSizeMinimum , FrameSampleCountHistogram = BPCM.Analysis.FrameSampleCountHistogram , FrameSet = BPCM.Analysis.FrameSet , CompressionUsed = BPCM.Analysis.CompressionUsed , CompressionUsedString = string.Join(", ", BPCM.Analysis.CompressionUsed.ToArray()) }); } }
public BPCMWaveProvider(BitstreamReader stream, double srf = 1) { streamBPCM = stream; frame0 = streamBPCM.Analysis.FrameSet[0]; currentFrame = frame0; vol = 1; srfactor = srf; int speedSR = (int)Math.Round(frame0.SamplingRate * srf, 0); waveFormat = WaveFormat.CreateCustomFormat(WaveFormatEncoding.Pcm, speedSR, frame0.Channels, speedSR * frame0.Channels * 2, frame0.Channels * 2, 16); rb = new RingBuffer(2097152); tsOffset = 0; }
public Player(string bpcmFile, ConfigurationBean config) { //First of all check the configs and if in doubt, use default values if (config.WaveOutDevice > WaveOut.DeviceCount || config.WaveOutDevice < 0) { config.WaveOutDevice = 0; } if (config.WaveOutBufferCount < 2) { config.WaveOutBufferCount = 2; } if (config.WaveOutBufferSize == 0) { config.WaveOutBufferSize = 100; } if (config.Volume > 1) { config.Volume = 1f; } if (config.Volume < 0) { config.Volume = 0f; } if (config.PlaybackRate == 0) { config.PlaybackRate = 1; } _config = config; //Open the BPCM file _BPCMFile = new FileStream(bpcmFile, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576, FileOptions.RandomAccess); _BPCMStream = new BitstreamReader(_BPCMFile); _BPCMStream.EnableDither = _config.EnableDithering; __INTERNAL_WaveOutInit(); //Determine the used device and fire the init event once. WaveOutCapabilities devcaps = WaveOut.GetCapabilities(0); for (int x = 0; x < WaveOut.DeviceCount; x++) { if (x == _WaveOut.DeviceNumber || x != 0) { devcaps = WaveOut.GetCapabilities(x); } } _config.WaveOutInitializedEvent?.Invoke(devcaps); }
protected virtual void Dispose(bool disposing) { if (!disposedValue) { if (disposing) { _WaveOut.Stop(); _WaveOut.Dispose(); _WaveOut = null; _BPCMFile.Dispose(); _BPCMFile = null; } _BPCMStream = null; _BPCMWaveProvider = null; disposedValue = true; } }
public static void DecodeBPCMFile(string bpcmFile, string waveFile, dgUpdate statusCallback = null, double updateInterval = 250, bool Analyze = true, dgBPCMFileOpened fileOpenedEvent = null, bool enableDither = true) { using (FileStream s = new FileStream(bpcmFile, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576, FileOptions.RandomAccess)) { BitstreamReader BPCM = new BitstreamReader(s); double duration = (double)BPCM.Analysis.DurationSampleCount / BPCM.Analysis.FrameSet[0].SamplingRate; TimeSpan dur = TimeSpan.FromSeconds(duration); string strDuration = String.Format("{0:00}d {1:00}h {2:00}m {3:00}s {4:000.000}ms", dur.Days, dur.Hours, dur.Minutes, dur.Seconds, (duration - Math.Floor(duration)) * 1000); fileOpenedEvent?.Invoke(new Info() { NumberOfChannels = BPCM.Analysis.FrameSet[0].Channels , SamplingRate = BPCM.Analysis.FrameSet[0].SamplingRate , Duration = BPCM.Analysis.Duration , DurationSampleCount = BPCM.Analysis.DurationSampleCount , DurationString = strDuration , BitrateMin = BPCM.Analysis.BitrateMinimum , BitrateAvg = BPCM.Analysis.BitrateAverage , BitrateMax = BPCM.Analysis.BitrateMaximum , BlockSizeNominal = BPCM.Analysis.BlockSizeNominal , BlockSizeAverage = BPCM.Analysis.BlockSizeAverage , BlockSizeMaximum = BPCM.Analysis.BlockSizeMaximum , BlockSizeMinimum = BPCM.Analysis.BlockSizeMinimum , FrameSampleCountHistogram = BPCM.Analysis.FrameSampleCountHistogram , FrameSet = BPCM.Analysis.FrameSet , CompressionUsed = BPCM.Analysis.CompressionUsed , CompressionUsedString = string.Join(", ", BPCM.Analysis.CompressionUsed.ToArray()) }); BPCM.EnableDither = enableDither; using (FileStream streamOut = new FileStream(waveFile, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.Read, 1048576, FileOptions.RandomAccess)) { WAVEWriter w = new WAVEWriter(streamOut, new WAVEFormat() { nSamplesPerSec = (uint)BPCM.Analysis.FrameSet[0].SamplingRate , nChannels = (ushort)BPCM.Analysis.FrameSet[0].Channels , nBitsPerSample = 16 , nBlockAlign = (ushort)(BPCM.Analysis.FrameSet[0].Channels * 2) , nAvgBytesPerSeconds = (uint)(BPCM.Analysis.FrameSet[0].Channels * 2 * BPCM.Analysis.FrameSet[0].SamplingRate) , wFormatTag = 1 //PCM signed integer Intel }); Stopwatch ssw = new Stopwatch(); ssw.Start(); double precisepos; TimeSpan pos; while (true) { object tmp = BPCM.GetFrame(); if (tmp.Equals(false)) { break; } Frame frame = (Frame)tmp; tmp = null; if (frame.Data?.Length > 0) { w.Write(frame.Data); } else { w.WriteSilence((double)frame.SampleCount / frame.SamplingRate); } if (ssw.ElapsedMilliseconds >= updateInterval && statusCallback != null) { precisepos = (double)w.PCMPosition / w.fmtHeader.nAvgBytesPerSeconds; pos = TimeSpan.FromSeconds(precisepos); statusCallback?.Invoke(new Status() { BytesWritten = w.PCMPosition , Position = (double)w.PCMPosition / w.fmtHeader.nAvgBytesPerSeconds , PositionString = String.Format("{0:00}d {1:00}h {2:00}m {3:00}s {4:000.000}ms", pos.Days, pos.Hours, pos.Minutes, pos.Seconds, (precisepos - Math.Floor(precisepos)) * 1000) , PercentageDone = (double)s.Position / s.Length * 100 }); ssw.Restart(); } if (BPCM.EOF) { break; } } precisepos = (double)w.PCMPosition / w.fmtHeader.nAvgBytesPerSeconds; pos = TimeSpan.FromSeconds(precisepos); statusCallback?.Invoke(new Status() { BytesWritten = w.PCMPosition , Position = (double)w.PCMPosition / w.fmtHeader.nAvgBytesPerSeconds , PositionString = String.Format("{0:00}d {1:00}h {2:00}m {3:00}s {4:000.000}ms", pos.Days, pos.Hours, pos.Minutes, pos.Seconds, (precisepos - Math.Floor(precisepos)) * 1000) , PercentageDone = 100 }); w.Finalize(); } } }
static void play(string bpcmfile, float volume, double rate, int output_device = 0, bool enDither = true) { Console.WriteLine("{0,-20} {1}", "File path:", new FileInfo(bpcmfile).Directory.FullName); Console.WriteLine("{0,-20} {1}", "File name:", new FileInfo(bpcmfile).Name); //Analyse stream FileStream s = new FileStream(bpcmfile, FileMode.Open, FileAccess.Read, FileShare.Read, 1048576, false); void AnalyisisProgress(float percentDone) { Console.CursorLeft = 0; Console.Write(String.Concat(Enumerable.Repeat(" ", Console.BufferWidth - 1))); Console.CursorLeft = 0; Console.Write("{0,-20} {1, -4:0.00} percent done.", "Analyzing file:", percentDone); } BitstreamReader p = new BitstreamReader(s, aupevt: AnalyisisProgress); p.EnableDither = enDither; Console.CursorLeft = 0; Console.Write(String.Concat(Enumerable.Repeat(" ", Console.BufferWidth - 1))); Console.CursorLeft = 0; //Print info Console.WriteLine("{0,-20} {1}", "File size:", Helpers.ByteFormatter.FormatBytes(s.Length)); printInfo(p.Analysis); Console.WriteLine(String.Concat(Enumerable.Repeat("\x2509", 80))); //Box line and corner as well as connection chars const string strFcF = "\x250C", strFc7 = "\x2510", strFcL = "\x2514", strFcJ = "\x2518" , strFbI = "\x2502", strFbL = "\x2500", strFbT = "\x252C", strFbUMT = "\x2534" , strFbLT = "\x251C", strFbMMT = "\x253C", strFbRT = "\x2524", strAUD = "\x2195", strALR = "\x2194"; //VU meter chars string[,] arrVU = { { " ", "\x2580" }, { " ", "\x2584" } }; //string[,] arrVU = { { " ", "\x25AE" }, { " ", "\x25AE" } }; int nBuffers = 16; int fnumLen = p.Analysis.FrameSet.Count.ToString().Length; if (fnumLen < 6) { fnumLen = 6; } int currFrame = 0; double currTS = 0; double speed = rate; bool dontExit = false; WaveOutEvent wavOut; BPCMWaveProvider bpcmWP = new BPCMWaveProvider(p, speed); //Prepare lines string strTsLine = String.Concat(Enumerable.Repeat(strFbL, 22)) , strFNumLine = String.Concat(Enumerable.Repeat(strFbL, fnumLen)) , strBitRateLine = String.Concat(Enumerable.Repeat(strFbL, 6)) , strComprLine = String.Concat(Enumerable.Repeat(strFbL, 10)) , strVolumeLine = String.Concat(Enumerable.Repeat(strFbL, 6)) , strRateLine = String.Concat(Enumerable.Repeat(strFbL, 5)) , strVULine = String.Concat(Enumerable.Repeat(strFbL, 58)) , strVUScale = "-\x221e\x2508" + "-60" + string.Concat(Enumerable.Repeat("\x2508", 6)) + "-50" + string.Concat(Enumerable.Repeat("\x2508", 6)) + "-40" + string.Concat(Enumerable.Repeat("\x2508", 7)) + "-30" + string.Concat(Enumerable.Repeat("\x2508", 6)) + "-20" + string.Concat(Enumerable.Repeat("\x2508", 4)) + "-12" + string.Concat(Enumerable.Repeat("\x2508", 2)) + "-6\x2508-3\x25080" , strVUBlank = String.Concat(Enumerable.Repeat(" ", 58)); void readDone(Frame CurrentFrame) { currFrame = CurrentFrame.FrameNumber; ADPCM.ADPCM4BIT.VolumeInfo vi = CurrentFrame.VolumeInfo; currTS = CurrentFrame.TimeStamp; TimeSpan tsnPos = TimeSpan.FromSeconds(currTS); int days = tsnPos.Days; if (days > 9) { days = 9; //Clamp days never to be more than 9. } string strPos = String.Format("{0}d {1:00}h {2:00}m {3:00}s {4:000}ms", days, tsnPos.Hours, tsnPos.Minutes, tsnPos.Seconds, tsnPos.Milliseconds); //Debug.WriteLine(strPos); //Refresh the status line Console.CursorLeft = 0; Console.Write(strFbI + strALR + "{0,21}" + strFbI + "{1," + fnumLen + "}" + strFbI + "{2,6}" + strFbI + "{3,-10}" + strFbI + strAUD + "{4,4}%" + strFbI + "x{5,4}" + strFbI, strPos, currFrame + 1, Math.Round(((CurrentFrame.DataLength + CurrentFrame.HederLength) / CurrentFrame.Duration) * 8, 0), CurrentFrame.CompressionTypeDescr, Math.Round(wavOut.Volume * 100, 1), speed); //Calculate bar length from dB value. const int max = 57, lowpoint = 62; //lowpoint is the positive value of the minus dB the scale will start int L = (int)Math.Round(((vi.dbPeakL + lowpoint) / lowpoint) * max); if (L < 0) { L = 0; //clamp } int R = (int)Math.Round(((vi.dbPeakR + lowpoint) / lowpoint) * max); if (R < 0) { R = 0; //clamp } string strVUMeterL = arrVU[0, 1] + String.Concat(Enumerable.Repeat(arrVU[0, 1], L)) + String.Concat(Enumerable.Repeat(arrVU[0, 0], max - L)); string strVUMeterR = arrVU[1, 1] + String.Concat(Enumerable.Repeat(arrVU[1, 1], R)) + String.Concat(Enumerable.Repeat(arrVU[1, 0], max - R)); Console.CursorLeft = 0; Console.CursorTop += 2; Console.WriteLine(strFbI + "L " + strVUMeterL + strFbI); Console.CursorLeft = 0; Console.CursorTop += 1; Console.WriteLine(strFbI + "R " + strVUMeterR + strFbI); Console.CursorLeft = 0; Console.CursorTop -= 5; } void woutInit() { wavOut = new WaveOutEvent(); wavOut.DeviceNumber = output_device; wavOut.DesiredLatency = 100 * wavOut.NumberOfBuffers; wavOut.PlaybackStopped += stopped; wavOut.Init(bpcmWP); wavOut.Volume = volume; nBuffers = wavOut.NumberOfBuffers; Console.CursorVisible = false; } woutInit(); bpcmWP.readDone = readDone; bpcmWP.volume = 1; void changeRate() { if (WaveOut.GetCapabilities(wavOut.DeviceNumber).SupportsPlaybackRateControl&& 1 == 2) { wavOut.Rate = (float)speed; } else { reinitBPCMWaveSrc(); } } void reinitBPCMWaveSrc() { dontExit = true; volume = wavOut.Volume; wavOut.Stop(); wavOut.Dispose(); wavOut = null; bpcmWP = null; GC.Collect(); bpcmWP = new BPCMWaveProvider(p, speed); bpcmWP.volume = 1; bpcmWP.readDone = readDone; p.Seek(currFrame - 1); //fix for skipping woutInit(); wavOut.Play(); } string DeviceName = ""; for (int x = 0; x < WaveOut.DeviceCount; x++) { if (x == wavOut.DeviceNumber) { DeviceName = "[" + x + "] " + WaveOut.GetCapabilities(x).ProductName; break; } } //Playback stopped event handler method void stopped(object sender, StoppedEventArgs e) { if (!dontExit) { exitNow(); } else { dontExit = false; } } //Drawing status box Console.WriteLine("{0,-20} {1}", "Playing on:", DeviceName); Console.WriteLine(""); //Upper corner of box Console.WriteLine(strFcF + strTsLine + strFbT + strFNumLine + strFbT + strBitRateLine + strFbT + strComprLine + strFbT + strVolumeLine + strFbT + strRateLine + strFc7); //Descriptions Console.WriteLine(strFbI + "{0,-22}" + strFbI + "{1,-" + fnumLen + "}" + strFbI + "{2,6}" + strFbI + "{3,-10}" + strFbI + "{4,-6}" + strFbI + "{5,-5}" + strFbI, "timestamp", "frame#", "bit/s", "compr.", "volume", "rate"); //Divider lines Console.WriteLine(strFbLT + strTsLine + strFbMMT + strFNumLine + strFbMMT + strBitRateLine + strFbMMT + strComprLine + strFbMMT + strVolumeLine + strFbMMT + strRateLine + strFbRT); //Status line (empty here) Console.WriteLine(strFbI + strALR + "{0,21}" + strFbI + "{1," + fnumLen + "}" + strFbI + "{2,6}" + strFbI + "{3,-10}" + strFbI + strAUD + "{4,4}%" + strFbI + "{5,5}" + strFbI, "N/A", "N/A", "N/A", "N/A", "N/A", "N/A"); //Bottom corner of box Console.WriteLine(strFbLT + strTsLine + strFbUMT + strFNumLine + strFbUMT + strBitRateLine + strFbUMT + strComprLine + strFbUMT + strVolumeLine + strFbUMT + strRateLine + strFbRT); //VU top //Console.WriteLine(strFcF + strFbL + strFbL + strVULine + strFc7); Console.WriteLine(strFbI + "L " + strVUBlank + strFbI); //VU middle Console.WriteLine(strFbI + " " + strVUScale + strFbI); Console.WriteLine(strFbI + "R " + strVUBlank + strFbI); //VU bottom Console.WriteLine(strFcL + strFbL + strFbL + strVULine + strFcJ); Console.WriteLine(""); Console.WriteLine("Volume: Up/Down 1% steps, Hold CTRL 0.1% steps, PgUp/PgDwn 10% steps"); Console.WriteLine("Seeking: Left/Right 5 secs, Hold CTRL 1 sec steps, Home for beginning"); Console.WriteLine("Speed: S, D and F. You'll figure it out. ;) You can also use CTRL!"); //Set cursor top position to the status line. Console.CursorTop -= 10; bool exit = false; void exitNow() { try { Console.CursorTop += 10; Console.CursorLeft = 0; Console.CursorVisible = true; wavOut?.Dispose(); p = null; } catch { } Environment.Exit(0); } //Start playback wavOut.Play(); //Crude key scanning while (!exit) { ConsoleKeyInfo key = Console.ReadKey(true); switch (key.Modifiers) { case ConsoleModifiers.Control: switch (key.Key) { case ConsoleKey.UpArrow: case ConsoleKey.VolumeUp: if (wavOut.Volume + 0.001f >= 1.0f) { wavOut.Volume = 1.0f; } else { wavOut.Volume += 0.001f; } break; case ConsoleKey.DownArrow: case ConsoleKey.VolumeDown: if (wavOut.Volume - 0.001f <= 0.0f) { wavOut.Volume = 0.0f; } else { wavOut.Volume -= 0.001f; } break; case ConsoleKey.LeftArrow: p.Seek(currTS - 1); bpcmWP.DropRingBuffer(); break; case ConsoleKey.RightArrow: p.Seek(currTS + 1); bpcmWP.DropRingBuffer(); break; case ConsoleKey.S: if (speed - 0.01 <= 0.01f) { speed = 0.01; } else { speed -= 0.01; } changeRate(); break; case ConsoleKey.F: if (speed + 0.01 >= 4.0f) { speed = 4.0; } else { speed += 0.01; } changeRate(); break; } break; default: switch (key.Key) { case ConsoleKey.UpArrow: case ConsoleKey.VolumeUp: if (wavOut.Volume + 0.01f >= 1.0f) { wavOut.Volume = 1.0f; } else { wavOut.Volume += 0.01f; } break; case ConsoleKey.DownArrow: case ConsoleKey.VolumeDown: if (wavOut.Volume - 0.01f <= 0.0f) { wavOut.Volume = 0.0f; } else { wavOut.Volume -= 0.01f; } break; case ConsoleKey.PageUp: if (wavOut.Volume + 0.1f >= 1.0f) { wavOut.Volume = 1.0f; } else { wavOut.Volume += 0.1f; } break; case ConsoleKey.PageDown: if (wavOut.Volume - 0.1f <= 0.0f) { wavOut.Volume = 0.0f; } else { wavOut.Volume -= 0.1f; } break; case ConsoleKey.LeftArrow: p.Seek(currTS - 5d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.RightArrow: p.Seek(currTS + 5d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D1: p.Seek(60d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D2: p.Seek(120d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D3: p.Seek(180d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D4: p.Seek(240d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D5: p.Seek(300d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D6: p.Seek(360d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D7: p.Seek(420d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D8: p.Seek(480d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D9: p.Seek(540d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.D0: p.Seek(600d); bpcmWP.DropRingBuffer(); break; case ConsoleKey.S: if (speed - 0.1 <= 0.01f) { speed = 0.01; } else { speed -= 0.1; } changeRate(); break; case ConsoleKey.D: speed = 1f; changeRate(); break; case ConsoleKey.F: if (speed + 0.1 >= 4.0f) { speed = 4.0; } else { speed += 0.1; } changeRate(); break; case ConsoleKey.Home: p.Seek(0); break; case ConsoleKey.Spacebar: switch (wavOut.PlaybackState) { case PlaybackState.Playing: wavOut.Pause(); break; case PlaybackState.Paused: wavOut.Play(); break; } break; case ConsoleKey.Escape: case ConsoleKey.Q: Console.CursorTop -= 10; exitNow(); break; } break; } } p = null; }