/// <summary> /// Returns whether or not any audio samples are remaining to be read from an array of WAV files. /// </summary> /// <param name="WAVFileArray">An array of WAVFile objects that are currently open</param> /// <returns>true if there are samples that can still be read from any of the files, or false if not.</returns> private static bool SamplesRemain(WAVFile[] WAVFileArray) { bool samplesRemain = false; if (WAVFileArray != null) { for (int i = 0; i < WAVFileArray.GetLength(0); ++i) { if (WAVFileArray[i].NumSamplesRemaining > 0) { samplesRemain = true; break; } } } return (samplesRemain); }
public static void MixAudioFiles(WAVInputFile[] pInputFiles, string pOutputFilename, string pTempDir) { amountDone = 0; OnUpdated(); string[] pFileList = new string[pInputFiles.Length]; for (int i = 0; i < pInputFiles.Length; i++) { pFileList[i] = pInputFiles[i].ToString(); } // If pFileList is null or empty, then just return. if (pFileList == null) return; if (pFileList.GetLength(0) == 0) return; // Make sure all the audio files have the sample rate (we can merge 8-bit and 16-bit audio and // convert mono to stereo). If the sample rates don't match, then throw an exception. if (!SampleRatesEqual(pFileList)) throw new WAVFileAudioMergeException("The sample rates of the audio files differ.", "WAVFile.MergeAudioFiles()"); amountDone = 1; OnUpdated(); // Check the audio format. If the number of bits/sample or sample rate is not // supported, then throw an exception. WAVFormat firstFileAudioFormat = GetAudioFormat(pFileList[0]); if (!SupportedBitsPerSample(firstFileAudioFormat.BitsPerSample)) throw new WAVFileBitsPerSampleException("Unsupported number of bits per sample: " + firstFileAudioFormat.BitsPerSample.ToString(), "WAVFile.MergeAudioFiles()", firstFileAudioFormat.BitsPerSample); if (!SupportedSampleRate(firstFileAudioFormat.SampleRateHz)) throw new WAVFileSampleRateException("Unsupported sample rate: " + firstFileAudioFormat.SampleRateHz.ToString(), "WAVFile.MergeAudioFiles()", firstFileAudioFormat.SampleRateHz); amountDone = 2; OnUpdated(); // 2. Create the temporary directory if it doesn't exist already. This checks for the // existence of the temp directory and stores the result in tempDirExisted so that // later, if the temp directory did not exist, we can delete it. bool tempDirExisted = Directory.Exists(pTempDir); if (!tempDirExisted) { try { Directory.CreateDirectory(pTempDir); if (!Directory.Exists(pTempDir)) throw new WAVFileAudioMergeException("Unable to create temporary work directory (" + pTempDir + ")", "WAVFile.MergeAudioFiles()"); } catch (System.Exception exc) { throw new WAVFileAudioMergeException("Unable to create temporary work directory (" + pTempDir + "): " + exc.Message, "WAVFile.MergeAudioFiles()"); } } amountDone = 3; OnUpdated(); // 4. Find the highest sample value of all files, and calculate the sound // multiplier based on this (all files will be scaled down by this amount). int numTracks = pFileList.GetLength(0); double multiplier = 0.0; // The multiplier for scaling down the audio files short highestSampleValue = 0; // Will store the highest sample value (8-bit will be cast to short) // Determine the highest # of bits per sample in all the audio files. short highestBitsPerSample = HighestBitsPerSample(pFileList); bool outputStereo = (HighestNumChannels(pFileList) > 1); if (highestBitsPerSample == 8) { // Get the highest sample value of all of the WAV files byte highestSample = HighestSampleValue_8bit(pFileList); highestSampleValue = (short)highestSample; byte difference = (byte)(highestSample - (byte.MaxValue / (byte)numTracks)); multiplier = 1.0 - ((double)difference / (double)highestSample); } else if (highestBitsPerSample == 16) { // Get the highest sample value of all of the WAV files highestSampleValue = HighestSampleValueAs16Bit(pFileList); short difference = (short)(highestSampleValue - (short.MaxValue / (short)numTracks)); multiplier = 1.0 - ((double)difference / (double)highestSampleValue); } if (double.IsInfinity(multiplier) || (multiplier == 0.0)) { // If the temp dir did not exist, then remove it. if (!tempDirExisted) DeleteDir(pTempDir); // Throw the exception throw new WAVFileAudioMergeException("Could not calculate first volume multiplier.", "WAVFile.MergeAudioFiles()"); } amountDone = 4; OnUpdated(); if (multiplier < 0.0) multiplier = -multiplier; // 5. Scale down the audio levels of the source files, and save the output // in the temp directory. Also change the path to the audio files in // inputFilenames so that they point to the temp directory (we'll be // combining the scaled audio files). // This array (scaledAudioFiles) will contain WAVFile objects for the scaled audio files. WAVFile[] scaledAudioFiles = new WAVFile[pFileList.GetLength(0)]; String filename = ""; // For the scaled-down WAV filename WAVFile inputFile = new WAVFile(); WAVFile outputFile = new WAVFile(); for (int i = 0; i < pFileList.GetLength(0); ++i) { // pFileList[i] contains the fully-pathed filename. Using just the // filename, construct the fully-pathed filename for the scaled-down // version of the file in the temporary directory. filename = pTempDir + "\\" + Path.GetFileName(pFileList[i]); // Copy the file to the temp directory, adjusting its bits/sample and number of // channels if necessary. And also ajust its volume using multiplier. //CopyAndConvert(pFileList[i], filename, highestBitsPerSample, outputStereo, multiplier); CopyAndConvert(pFileList[i], filename, highestBitsPerSample, outputStereo, 1.0f); // Create the WAVFile object in the scaledAudioFiles array, and open the scaled // audio file with it. scaledAudioFiles[i] = new WAVFile(); scaledAudioFiles[i].Open(filename, WAVFileMode.READ); } amountDone = 5; OnUpdated(); // 7. Now, create the final audio mix file. outputFile.Create(pOutputFilename, outputStereo, firstFileAudioFormat.SampleRateHz, highestBitsPerSample); amountDone = 6; OnUpdated(); // 8. Do the merging.. // The basic algorithm for doing the merging is as follows: // while there is at least 1 sample remaining in any of the source files // sample = 0 // for each source file // if the source file has any samples remaining // sample = sample + next available sample from the source file // sample = sample / # of source files // write the sample to the output file if (highestBitsPerSample == 8) { byte sample = 0; while (SamplesRemain(scaledAudioFiles)) { sample = 0; for (int i = 0; i < scaledAudioFiles.GetLength(0); ++i) { if (scaledAudioFiles[i].NumSamplesRemaining > 0) sample += scaledAudioFiles[i].GetNextSample_8bit(); } sample /= (byte)(scaledAudioFiles.GetLength(0)); outputFile.AddSample_8bit(sample); } } else if (highestBitsPerSample == 16) { for (int i = 0; i < scaledAudioFiles.Length - 1; i++) { int currentAudioFile = i; double totalFileTime = GetWavFileDuration(pFileList[currentAudioFile]).TotalSeconds; double timePerSample = totalFileTime / scaledAudioFiles[currentAudioFile].NumSamples; // in seconds double mixTime = totalFileTime - pInputFiles[currentAudioFile].FadeTime; double timePast = (scaledAudioFiles[currentAudioFile].NumSamples - scaledAudioFiles[currentAudioFile].NumSamplesRemaining) * timePerSample; while (scaledAudioFiles[currentAudioFile].NumSamplesRemaining > 0) { // Get the next sample from the current audio file short sample = scaledAudioFiles[currentAudioFile].GetNextSampleAs16Bit(); // Find the mix time to mix the sample of the next audio file into there if (timePast >= mixTime) { sample += scaledAudioFiles[currentAudioFile + 1].GetNextSampleAs16Bit(); } // Devide the sample by the amount of samples sample /= (short)(scaledAudioFiles.Length); // Write the audio sample to the output file outputFile.AddSample_16bit(sample); // Add the time of the sample ot the total time timePast += timePerSample; } amountDone = 94 / scaledAudioFiles.Length * (i + 1) + 6; OnUpdated(); } // Write the rest while (scaledAudioFiles[scaledAudioFiles.Length -1].NumSamplesRemaining > 0) { short sample = scaledAudioFiles[scaledAudioFiles.Length - 1].GetNextSampleAs16Bit(); sample /= (short)(scaledAudioFiles.Length); outputFile.AddSample_16bit(sample); } amountDone = 99; OnUpdated(); } PBBRenderer.Instance.ActiveStatus = 2; OnUpdated(); outputFile.Close(); // 9. Remove the input files(to free up disk space. foreach (WAVFile audioFile in scaledAudioFiles) { filename = audioFile.Filename; audioFile.Close(); System.IO.File.Delete(filename); } // 10. Now, increase the volume level of the output file. (will first have // to see if the inputs are 8-bit or 16-bit.) // Adjust the volume level of the file so that its volume is // the same as the volume of the input files. This is done by // first finding the highest sample value of all files, then // the highest sample value of the combined audio file, and // scaling the audio of the output file to match the volume // of the input files (such that the highest level of the output // file is the same as the highest level of all files). // For 16-bit audio, this works okay, but for 8-bit audio, the // output seems to sound better if we adjust the volume so that // the highest sample value is 3/4 of the maximum sample value // for the # of bits/sample. if (highestBitsPerSample == 8) { byte highestSampleVal = HighestSampleValue_8bit(pOutputFilename); byte maxValue = byte.MaxValue / 4 * 3; multiplier = (double)maxValue / (double)highestSampleVal; } else if (highestBitsPerSample == 16) { short finalMixFileHighestSample = HighestSampleValueAs16Bit(pOutputFilename); // Calculate the multiplier for adjusting the audio of the final mix file. //short difference = (short)(finalMixFileHighestSample - highestSampleValue); //multiplier = 1.0 - ((double)difference / (double)finalMixFileHighestSample); // This calculates the multiplier based on the highest sample value in the audio // file and the highest possible 16-bit sample value. multiplier = (double)short.MaxValue / (double)finalMixFileHighestSample; } if (multiplier < 0.0) multiplier = -multiplier; // Adjust the volume of the final mix file. //AdjustVolumeInPlace(pOutputFilename, multiplier); // If the temporary directory did not exist prior to this method being called, then // delete it. if (!tempDirExisted) { String retval = DeleteDir(pTempDir); if (retval != "") throw new WAVFileAudioMergeException("Unable to remove temp directory (" + pTempDir + "): " + retval, "WAVFile.MergeAudioFiles()"); } amountDone = 100; }