/// <summary> /// This method takes the left channel and right channel wave raw data and analyses it to get /// the maximum and minimum values in the float structure. It returns a data structure named /// WaveDataMinMax (see class description for more information). Negative values can be converted to /// positive values before min and max comparaison. Set this parameter to true for output meters and /// false for wave form display controls. /// </summary> /// <param name="waveDataLeft">Raw wave data (left channel)</param> /// <param name="waveDataRight">Raw wave data (right channel)</param> /// <param name="convertNegativeToPositive">Convert negative values to positive values (ex: true when used for output meters, /// false when used with wave form display controls (since the negative value is used to draw the bottom end of the waveform).</param> /// <returns>WaveDataMinMax data structure</returns> public static WaveDataMinMax GetMinMaxFromWaveData(float[] waveDataLeft, float[] waveDataRight, bool convertNegativeToPositive) { // Create default data WaveDataMinMax data = new WaveDataMinMax(); // Loop through values to get min/max for (int i = 0; i < waveDataLeft.Length; i++) { // Set values to compare float left = waveDataLeft[i]; float right = waveDataRight[i]; // Do we have to convert values before comparaison? if (convertNegativeToPositive) { // Compare values, if negative then remove negative sign if (left < 0) { left = -left; } if (right < 0) { right = -right; } } // Calculate min/max for left channel if (left < data.leftMin) { data.leftMin = left; } if (left > data.leftMax) { data.leftMax = left; } // Calculate min/max for right channel if (right < data.rightMin) { data.rightMin = right; } if (right > data.rightMax) { data.rightMax = right; } // Calculate min/max mixing both channels if (left < data.mixMin) { data.mixMin = left; } if (right < data.mixMin) { data.mixMin = right; } if (left > data.mixMax) { data.mixMax = left; } if (right > data.mixMax) { data.mixMax = right; } } return data; }
/// <summary> /// Reads a peak file and returns a min/max peak list. /// </summary> /// <param name="peakFilePath">Peak file path</param> /// <returns>List of min/max peaks</returns> public List<WaveDataMinMax> ReadPeakFile(string peakFilePath) { // Declare variables FileStream fileStream = null; GZipStream gzipStream = null; BinaryReader binaryReader = null; List<WaveDataMinMax> listMinMax = new List<WaveDataMinMax>(); string fileHeader = null; long audioFileLength = 0; int chunkSize = 0; int numberOfBlocks = 0; int currentBlock = 0; try { // Create file stream fileStream = new FileStream(peakFilePath, FileMode.Open, FileAccess.Read); binaryReader = new BinaryReader(fileStream); gzipStream = new GZipStream(fileStream, CompressionMode.Decompress); // Read file header (34 characters) // Ex: Sessions Peak File (version# 1.00) fileHeader = new string(binaryReader.ReadChars(35)); // Extract version and validate string version = fileHeader.Substring(fileHeader.Length - 5, 4); if (version != _version) throw new PeakFileFormatIncompatibleException("Error: The peak file format is not compatible. Expecting version " + _version + " instead of version " + version + ".", null); // Read audio file length audioFileLength = binaryReader.ReadInt64(); chunkSize = binaryReader.ReadInt32(); numberOfBlocks = binaryReader.ReadInt32(); // Loop through data while (binaryReader.PeekChar() != -1) { currentBlock++; // Read peak information and add to list WaveDataMinMax peak = new WaveDataMinMax(); peak.leftMin = (float)binaryReader.ReadDouble(); peak.leftMax = (float)binaryReader.ReadDouble(); peak.rightMin = (float)binaryReader.ReadDouble(); peak.rightMax = (float)binaryReader.ReadDouble(); peak.mixMin = (float)binaryReader.ReadDouble(); peak.mixMax = (float)binaryReader.ReadDouble(); listMinMax.Add(peak); } // Validate number of blocks read if (currentBlock < numberOfBlocks - 1) throw new PeakFileCorruptedException("Error: The peak file is corrupted (the number of blocks didn't match)!", null); } catch (Exception ex) { throw new PeakFileCorruptedException("Error: The peak file is corrupted!", ex); } finally { gzipStream.Close(); binaryReader.Close(); fileStream.Close(); } return listMinMax; }
private void RequestBitmapInternal(Object stateInfo) { // Use this instead of a task, this guarantees to execute in another thread //Console.WriteLine("WaveFormRenderingService - RequestBitmap - boundsBitmap: {0} boundsWaveForm: {1} zoom: {2}", boundsBitmap, boundsWaveForm, zoom); var request = stateInfo as WaveFormBitmapRequest; IMemoryGraphicsContext context; try { //Console.WriteLine("WaveFormRenderingService - Creating image cache..."); context = _memoryGraphicsContextFactory.CreateMemoryGraphicsContext(request.BoundsBitmap.Width, request.BoundsBitmap.Height); if (context == null) { Console.WriteLine("Error initializing image cache context!"); return; } } catch (Exception ex) { Console.WriteLine("Error while creating image cache context: " + ex.Message); return; } IBasicImage imageCache; try { float x1 = 0; float x2 = 0; float leftMin = 0; float leftMax = 0; float rightMin = 0; float rightMax = 0; float mixMin = 0; float mixMax = 0; int historyIndex = 0; int historyCount = _waveDataCache.Count; float lineWidth = 0; int nHistoryItemsPerLine = 0; const float desiredLineWidth = 0.5f; WaveDataMinMax[] subset = null; // Find out how many samples are represented by each line of the wave form, depending on its width. // For example, if the history has 45000 items, and the control has a width of 1000px, 45 items will need to be averaged by line. float lineWidthPerHistoryItem = request.BoundsWaveForm.Width / (float)historyCount; // Check if the line width is below the desired line width if (lineWidthPerHistoryItem < desiredLineWidth) { // Try to get a line width around 0.5f so the precision is good enough and no artifacts will be shown. while (lineWidth < desiredLineWidth) { // Increment the number of history items per line //Console.WriteLine("Determining line width (lineWidth: " + lineWidth.ToString() + " desiredLineWidth: " + desiredLineWidth.ToString() + " nHistoryItemsPerLine: " + nHistoryItemsPerLine.ToString() + " lineWidthPerHistoryItem: " + lineWidthPerHistoryItem.ToString()); nHistoryItemsPerLine++; lineWidth += lineWidthPerHistoryItem; } nHistoryItemsPerLine--; lineWidth -= lineWidthPerHistoryItem; } else { // The lines are larger than 0.5 pixels. lineWidth = lineWidthPerHistoryItem; nHistoryItemsPerLine = 1; } float heightToRenderLine = 0; if (request.DisplayType == WaveFormDisplayType.Stereo) heightToRenderLine = (request.BoundsWaveForm.Height / 4); else heightToRenderLine = (request.BoundsWaveForm.Height / 2); context.DrawRectangle(new BasicRectangle(0, 0, request.BoundsBitmap.Width + 2, request.BoundsBitmap.Height), _brushBackground, _penTransparent); // The pen cannot be cached between refreshes because the line width changes every time the width changes //context.SetLineWidth(0.2f); var penWaveForm = new BasicPen(new BasicBrush(_colorWaveForm), lineWidth); context.SetPen(penWaveForm); float startLine = ((int)Math.Floor(request.BoundsBitmap.X / lineWidth)) * lineWidth; historyIndex = (int) ((startLine / lineWidth) * nHistoryItemsPerLine); //Console.WriteLine("WaveFormRenderingService - startLine: {0} boundsWaveForm.Width: {1} nHistoryItemsPerLine: {2} historyIndex: {3}", startLine, boundsWaveForm.Width, nHistoryItemsPerLine, historyIndex); //List<float> roundValues = new List<float>(); float lastLine = startLine + request.BoundsBitmap.Width; if (request.BoundsWaveForm.Width - request.BoundsBitmap.X < request.BoundsBitmap.Width) lastLine = request.BoundsWaveForm.Width; //context.DrawText(string.Format("{0:0.0}", startLine), new BasicPoint(1, request.BoundsBitmap.Height - 10), new BasicColor(255, 255, 255), "Roboto Bold", 10 * context.Density); //context.DrawText(string.Format("{0:0.0}", lastLine), new BasicPoint(1, request.BoundsBitmap.Height - 22), new BasicColor(255, 255, 255), "Roboto Bold", 10 * context.Density); //context.DrawText(string.Format("{0:0.0}", request.BoundsBitmap.X), new BasicPoint(1, request.BoundsBitmap.Height - 34), new BasicColor(255, 255, 255), "Roboto Bold", 10 * context.Density); //context.DrawText(string.Format("{0:0.0}", request.BoundsWaveForm.Width), new BasicPoint(1, request.BoundsBitmap.Height - 46), new BasicColor(255, 255, 255), "Roboto Bold", 10 * context.Density); //context.DrawText(string.Format("{0}", request.BoundsBitmap.X), new BasicPoint(1, request.BoundsBitmap.Height - 20), new BasicColor(255, 255, 255), "Roboto Bold", 10 * context.Density); //context.DrawText(string.Format("{0:0.0}", request.Zoom), new BasicPoint(1, request.BoundsBitmap.Height - 10), new BasicColor(255, 255, 255), "Roboto Bold", 10 * context.Density); for (float i = startLine; i < lastLine; i += lineWidth) { #if MACOSX // On Mac, the pen needs to be set every time we draw or the color might change to black randomly (weird?) context.SetPen(penWaveForm); #endif // COMMENTED possible solution for varying line widths rendering problem // Round to 0.5 //i = (float)Math.Round(i * 2) / 2; //float iRound = (float)Math.Round(i); //float iRound = (float)Math.Round(i * 2) / 2; //float iRound = (float)Math.Round(i * 4) / 4; // // If this value has already been drawn, skip it (this happens because of the rounding, and this fixes a visual bug) // if(roundValues.Contains(iRound)) // { // // Increment the history index; pad the last values if the count is about to exceed // if (historyIndex < historyCount - 1) // historyIndex += nHistoryItemsPerLine; // continue; // } // else // { // roundValues.Add(iRound); // } // Determine the maximum height of a line (+/-) //Console.WriteLine("WaveForm - Rendering " + i.ToString() + " (rnd=" + iRound.ToString() + ") on " + widthAvailable.ToString()); // Determine x position // x1 = iRound; //i; // x2 = iRound; //i; x1 = i - startLine; x2 = i - startLine; if (nHistoryItemsPerLine > 1) { if (historyIndex + nHistoryItemsPerLine > historyCount) { // Create subset with remaining data subset = new WaveDataMinMax[historyCount - historyIndex]; _waveDataCache.CopyTo(historyIndex, subset, 0, historyCount - historyIndex); } else { subset = new WaveDataMinMax[nHistoryItemsPerLine]; _waveDataCache.CopyTo(historyIndex, subset, 0, nHistoryItemsPerLine); } leftMin = AudioTools.GetMinPeakFromWaveDataMaxHistory(subset.ToList(), nHistoryItemsPerLine, ChannelType.Left); leftMax = AudioTools.GetMaxPeakFromWaveDataMaxHistory(subset.ToList(), nHistoryItemsPerLine, ChannelType.Left); rightMin = AudioTools.GetMinPeakFromWaveDataMaxHistory(subset.ToList(), nHistoryItemsPerLine, ChannelType.Right); rightMax = AudioTools.GetMaxPeakFromWaveDataMaxHistory(subset.ToList(), nHistoryItemsPerLine, ChannelType.Right); mixMin = AudioTools.GetMinPeakFromWaveDataMaxHistory(subset.ToList(), nHistoryItemsPerLine, ChannelType.Mix); mixMax = AudioTools.GetMaxPeakFromWaveDataMaxHistory(subset.ToList(), nHistoryItemsPerLine, ChannelType.Mix); } else { leftMin = _waveDataCache[historyIndex].leftMin; leftMax = _waveDataCache[historyIndex].leftMax; rightMin = _waveDataCache[historyIndex].rightMin; rightMax = _waveDataCache[historyIndex].rightMax; mixMin = _waveDataCache[historyIndex].mixMin; mixMax = _waveDataCache[historyIndex].mixMax; } float leftMaxHeight = leftMax * heightToRenderLine; float leftMinHeight = leftMin * heightToRenderLine; float rightMaxHeight = rightMax * heightToRenderLine; float rightMinHeight = rightMin * heightToRenderLine; float mixMaxHeight = mixMax * heightToRenderLine; float mixMinHeight = mixMin * heightToRenderLine; //Console.WriteLine("WaveFormRenderingService - line: {0} x1: {1} x2: {2} historyIndex: {3} historyCount: {4} width: {5}", i, x1, x2, historyIndex, historyCount, boundsWaveForm.Width); if (request.DisplayType == WaveFormDisplayType.LeftChannel || request.DisplayType == WaveFormDisplayType.RightChannel || request.DisplayType == WaveFormDisplayType.Mix) { // Calculate min/max line height float minLineHeight = 0; float maxLineHeight = 0; // Set mib/max if (request.DisplayType == WaveFormDisplayType.LeftChannel) { minLineHeight = leftMinHeight; maxLineHeight = leftMaxHeight; } else if (request.DisplayType == WaveFormDisplayType.RightChannel) { minLineHeight = rightMinHeight; maxLineHeight = rightMaxHeight; } else if (request.DisplayType == WaveFormDisplayType.Mix) { minLineHeight = mixMinHeight; maxLineHeight = mixMaxHeight; } // Positive Max Value - Draw positive value (y: middle to top) context.StrokeLine(new BasicPoint(x1, heightToRenderLine), new BasicPoint(x2, heightToRenderLine - maxLineHeight)); // Negative Max Value - Draw negative value (y: middle to height) context.StrokeLine(new BasicPoint(x1, heightToRenderLine), new BasicPoint(x2, heightToRenderLine + (-minLineHeight))); } else if (request.DisplayType == WaveFormDisplayType.Stereo) { // LEFT Channel - Positive Max Value - Draw positive value (y: middle to top) context.StrokeLine(new BasicPoint(x1, heightToRenderLine), new BasicPoint(x2, heightToRenderLine - leftMaxHeight)); // LEFT Channel - Negative Max Value - Draw negative value (y: middle to height) context.StrokeLine(new BasicPoint(x1, heightToRenderLine), new BasicPoint(x2, heightToRenderLine + (-leftMinHeight))); // RIGHT Channel - Positive Max Value (Multiply by 3 to get the new center line for right channel) - Draw positive value (y: middle to top) context.StrokeLine(new BasicPoint(x1, (heightToRenderLine * 3)), new BasicPoint(x2, (heightToRenderLine * 3) - rightMaxHeight)); // RIGHT Channel - Negative Max Value - Draw negative value (y: middle to height) context.StrokeLine(new BasicPoint(x1, (heightToRenderLine * 3)), new BasicPoint(x2, (heightToRenderLine * 3) + (-rightMinHeight))); } // Increment the history index; pad the last values if the count is about to exceed if (historyIndex < historyCount - 1) historyIndex += nHistoryItemsPerLine; } } catch (Exception ex) { Console.WriteLine("Error while creating image cache: " + ex.Message); } finally { // Get image from context (at this point, we are sure the image context has been initialized properly) //Console.WriteLine("WaveFormRenderingService - Rendering image to memory..."); context.Close(); imageCache = context.RenderToImageInMemory(); } //Console.WriteLine("WaveFormRenderingService - Created image successfully."); //stopwatch.Stop(); //Console.WriteLine("WaveFormRenderingService - Created image successfully in {0} ms.", stopwatch.ElapsedMilliseconds); OnGenerateWaveFormBitmapEnded(new GenerateWaveFormEventArgs() { //AudioFilePath = audioFile.FilePath, OffsetX = request.BoundsBitmap.X, Zoom = request.Zoom, Width = context.BoundsWidth, DisplayType = request.DisplayType, Image = imageCache }); }