/// <summary> /// Generates a peak file for an audio file. /// Note: BASS should be initialized already before calling this method. This uses a decode stream. /// </summary> /// <param name="audioFilePath">Audio file path</param> /// <param name="peakFilePath">Peak file path</param> public void GeneratePeakFile(string audioFilePath, string peakFilePath) { #if ANDROID int[] buffer = null; #else float[] buffer = null; #endif bool cancelled = false; FileStream fileStream = null; BinaryWriter binaryWriter = null; GZipStream gzipStream = null; int chunkSize = 0; int currentBlock = 0; long audioFileLength = 0; int read = 0; long bytesRead = 0; float[] floatLeft = null; float[] floatRight = null; IntPtr data = new IntPtr(); // initialized properly later WaveDataMinMax minMax = null; List<WaveDataMinMax> listMinMaxForProgressData = new List<WaveDataMinMax>(); IsLoading = true; bool processSuccessful = false; _currentTask = Task.Factory.StartNew(() => { try { // Get audio file length and divide it by two since we're using floating point Channel channelDecode = Channel.CreateFileStreamForDecoding(audioFilePath, _useFloatingPoint); audioFileLength = channelDecode.GetLength(); audioFileLength /= 2; // Delete any previous peak file if (File.Exists(peakFilePath)) File.Delete(peakFilePath); // Create streams and binary writer fileStream = new FileStream(peakFilePath, FileMode.Create, FileAccess.Write); binaryWriter = new BinaryWriter(fileStream); gzipStream = new GZipStream(fileStream, CompressionMode.Compress); // 4096 bytes for 16-bit PCM data chunkSize = 4096; // How many blocks will there be? double blocks = Math.Ceiling(((double)audioFileLength / (double)chunkSize) * 2) + 1; // Write file header (34 characters) // 123456789012345678901234567890 // Sessions Peak File (version# 1.00) string version = "Sessions Peak File (version# " + _version + ")"; binaryWriter.Write(version); // Write audio file length binaryWriter.Write(audioFileLength); binaryWriter.Write((Int32)chunkSize); binaryWriter.Write((Int32)blocks); // Create buffer data = Marshal.AllocHGlobal(chunkSize); #if ANDROID buffer = new int[chunkSize]; #else buffer = new float[chunkSize]; #endif // Is an event binded to OnProcessData? if (OnProcessStarted != null) { PeakFileStartedData dataStarted = new PeakFileStartedData(){ Length = audioFileLength, TotalBlocks = (Int32)blocks }; OnProcessStarted(dataStarted); } // Loop through file using chunk size int dataBlockRead = 0; do { // Check for cancel //Console.WriteLine("PeakFileService - Bytes read: " + bytesRead.ToString()); if (_cancellationToken.IsCancellationRequested) { // Set flags, exit loop Console.WriteLine("PeakFileGenerator - Cancelling..."); cancelled = true; IsLoading = false; OnProcessDone(new PeakFileDoneData() { AudioFilePath = audioFilePath, Cancelled = true }); break; } // Get data and increment bytes read read = channelDecode.GetData(buffer, chunkSize); bytesRead += read; // Create arrays for left and right channel floatLeft = new float[chunkSize / 2]; floatRight = new float[chunkSize / 2]; // Loop through sample data to split channels for (int a = 0; a < chunkSize; a++) { if (_useFloatingPoint) { // Check if left or right channel if (a%2 == 0) floatLeft[a/2] = buffer[a]; else floatRight[a/2] = buffer[a]; } else { #if ANDROID // Get left/right channel values short leftValue = Base.LowWord(buffer[a]); short rightValue = Base.HighWord(buffer[a]); floatLeft[a/2] = (float)leftValue / (float)Int16.MaxValue; floatRight[a/2] = (float)rightValue / (float)Int16.MaxValue; #endif } } // Calculate min/max and add it to the min/max list for event progress minMax = AudioTools.GetMinMaxFromWaveData(floatLeft, floatRight, false); listMinMaxForProgressData.Add(minMax); // Write peak information binaryWriter.Write((double)minMax.leftMin); binaryWriter.Write((double)minMax.leftMax); binaryWriter.Write((double)minMax.rightMin); binaryWriter.Write((double)minMax.rightMax); binaryWriter.Write((double)minMax.mixMin); binaryWriter.Write((double)minMax.mixMax); // Update progress every X blocks (m_progressReportBlockInterval) default = 20 dataBlockRead += read; if (dataBlockRead >= read * progressReportBlockInterval) { // Reset flag dataBlockRead = 0; // Report progress PeakFileProgressData dataProgress = new PeakFileProgressData(); dataProgress.AudioFilePath = audioFilePath; dataProgress.PeakFilePath = peakFilePath; dataProgress.PercentageDone = (((float)bytesRead / (float)audioFileLength) / 2) * 100; dataProgress.Length = audioFileLength; dataProgress.CurrentBlock = currentBlock; dataProgress.TotalBlocks = (Int32)blocks; dataProgress.MinMax = listMinMaxForProgressData; OnProcessData(dataProgress); // Reset min/max list listMinMaxForProgressData = new List<WaveDataMinMax>(); } currentBlock++; } while (read == chunkSize); // Free channel Console.WriteLine("PeakFileService - Freeing channel..."); channelDecode.Free(); // TODO: This should replace the IsCancelled status since cancelling the task doesn't go end well Console.WriteLine("PeakFileService - Is process successful? bytesRead: " + bytesRead.ToString() + " audioFileLength: " + audioFileLength.ToString()); if(bytesRead >= audioFileLength) processSuccessful = true; // Set nulls for garbage collection channelDecode = null; floatLeft = null; floatRight = null; buffer = null; minMax = null; } catch (Exception ex) { // Return exception //e.Result = ex; Console.WriteLine("PeakFileService - Error: " + ex.Message); } finally { // Close writer and stream Console.WriteLine("PeakFileService - Closing file stream..."); if (gzipStream != null) gzipStream.Close(); if (binaryWriter != null) binaryWriter.Close(); if (fileStream != null) fileStream.Close(); gzipStream = null; binaryWriter = null; fileStream = null; // If the operation was cancelled, delete the files if (cancelled) { // Check if file exists if (File.Exists(peakFilePath)) { try { // Delete file File.Delete(peakFilePath); } catch { // Just skip this step. Tracing.Log("Could not delete peak file " + peakFilePath + "."); } } } } // Set completed IsLoading = false; Console.WriteLine("PeakFileService - ProcessDone - processSuccessful: " + processSuccessful.ToString() + " filePath: " + audioFilePath); OnProcessDone(new PeakFileDoneData() { AudioFilePath = audioFilePath, Cancelled = !processSuccessful }); }, _cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current); }
void HandleOnPeakFileProcessData(PeakFileProgressData data) { //Console.WriteLine("WaveFormRenderingService - HandleOnPeakFileProcessData"); OnGeneratePeakFileProgress(new GeneratePeakFileEventArgs() { AudioFilePath = data.AudioFilePath, PercentageDone = data.PercentageDone }); }