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; }
public byte[] decode(byte[] adpcmIn, out ADPCM4BIT.VolumeInfo vi, bool enableInloopVolumeStats, float volume = 1f) { uint i, pO; byte[] Output = new byte[(adpcmIn.Length - 3) * 4]; int t; int sign; // Current adpcm sign bit int delta; // Current adpcm output value Mid int step; // Stepsize int valprev; // virtual previous output value Mid int vpdiff; // Current change to valprev Side int index; // Current step change index Mid int bitbuffer; // place to keep next 4-bit value uint pv = 0; //peak volume uint av = 0; //average volume vi = new ADPCM4BIT.VolumeInfo(); vi.dbPeakL = double.NegativeInfinity; vi.dbPeakR = double.NegativeInfinity; vi.dbAvgL = double.NegativeInfinity; vi.dbAvgR = double.NegativeInfinity; try { valprev = BitConverter.ToInt16(adpcmIn, 0); index = adpcmIn[2]; step = stepsizeTable[index]; for (i = 3; i <= adpcmIn.Length - 1; i++) { pO = (i - 3) * 4; //**** Step 1 - get the delta value bitbuffer = adpcmIn[i]; delta = bitbuffer >> 4; //**** Step 2 - Find new index value (for later) index += indexTable[delta]; if (index < 0) { index = 0; } else if (index > 88) { index = 88; } //**** Step 3 - Separate sign and magnitude sign = delta & 8; delta = delta & 7; //**** Step 4 - update output value vpdiff = (delta * step) >> 2; if (sign == 8) { valprev -= vpdiff; } else { valprev += vpdiff; } //**** Step 5 - clamp output value if (valprev > short.MaxValue) { valprev = short.MaxValue; } else if (valprev < short.MinValue) { valprev = short.MinValue; } //**** Step 6 - Update step value step = stepsizeTable[index]; //Volume processing (crude but it works!!) t = valprev; if (volume != 1) { t = (int)Math.Round(t * volume, 0); } if (t < Int16.MinValue) { t = Int16.MinValue; } if (t > Int16.MaxValue) { t = Int16.MaxValue; } BitConverter.GetBytes((short)t).CopyTo(Output, pO); //NEXT SAMPLE //**** Step 1 - get the delta value delta = bitbuffer & 0xF; //**** Step 2 - Find new index value (for later) index += indexTable[delta]; if (index < 0) { index = 0; } else if (index > 88) { index = 88; } //**** Step 3 - Separate sign and magnitude sign = delta & 8; delta = delta & 7; //**** Step 4 - update output value vpdiff = (delta * step) >> 2; if (sign == 8) { valprev -= vpdiff; } else { valprev += vpdiff; } //**** Step 5 - clamp output value if (valprev > short.MaxValue) { valprev = short.MaxValue; } else if (valprev < short.MinValue) { valprev = short.MinValue; } //**** Step 6 - Update step value step = stepsizeTable[index]; t = valprev; //If desired, do the audio analyisis //Inloop volume analysis! if (enableInloopVolumeStats) { //Converting to absolute value //Abs and conversion may be slow, so we do this once and write it into variables uint tt = (uint)Math.Abs(t); //determine peak value if (pv < tt) { pv = tt; } //calculating average volume av = (uint)Math.Round((av + tt * 2) / 3d); } //Volume processing (crude but it works!!) if (volume != 1) { t = (int)Math.Round(t * volume, 0); } if (t < Int16.MinValue) { t = Int16.MinValue; } if (t > Int16.MaxValue) { t = Int16.MaxValue; } BitConverter.GetBytes((short)t).CopyTo(Output, pO + 2); } if (enableInloopVolumeStats) { vi.dbPeakL = 20 * Math.Log10((double)pv / short.MaxValue); vi.dbPeakR = 20 * Math.Log10((double)pv / short.MaxValue); vi.dbAvgL = 20 * Math.Log10((double)av / short.MaxValue); vi.dbAvgR = 20 * Math.Log10((double)av / short.MaxValue); } } catch { } return(Output); }