static void ApplyPitch(PlayItem item) { // Get info about the WAV. int sampleRate = item.WavHead.SampleRate; int bitsPerSample = item.WavHead.BitsPerSample; int channelCount = item.WavHead.Channels; // Get info about effects and pitch. var ms = new MemoryStream(); var writer = new System.IO.BinaryWriter(ms); var bytes = item.WavData; // Add 100 milliseconds at the start. var silenceStart = 100; // Add 200 milliseconds at the end. var silenceEnd = 200; var silenceBytes = AudioHelper.GetSilenceByteCount(sampleRate, bitsPerSample, channelCount, silenceStart + silenceEnd); // Comment WriteHeader(...) line, because SharpDX don't need that (it creates noise). //AudioHelper.WriteHeader(writer, bytes.Length + silenceBytes, channelCount, sampleRate, bitsPerSample); if (ApplyEffects) { token = new CancellationTokenSource(); // This part could take long time. bytes = EffectsGeneral.ApplyPitchShift(bytes, channelCount, sampleRate, bitsPerSample, PitchShift, token); // If pitch shift was canceled then... if (token.IsCancellationRequested) { return; } } // Add silence at the start to make room for effects. Audio.AudioHelper.WriteSilenceBytes(writer, sampleRate, bitsPerSample, channelCount, silenceStart); writer.Write(bytes); // Add silence at the back to make room for effects. Audio.AudioHelper.WriteSilenceBytes(writer, sampleRate, bitsPerSample, channelCount, silenceEnd); // Add result to play list. item.WavData = ms.ToArray(); //System.IO.File.WriteAllBytes("Temp.wav", item.Data); var duration = ((decimal)bytes.Length * 8m) / (decimal)channelCount / (decimal)sampleRate / (decimal)bitsPerSample * 1000m; duration += (silenceStart + silenceEnd); item.Duration = (int)duration; }
/// <summary> /// Load sound data. wavData must not contain WAV Head. /// </summary> /// <param name="wavBytes"></param> /// <returns>Returns duration.</returns> public decimal Load(byte[] wavBytes, int sampleRate, int bitsPerSample, int channelCount) { var format = new SharpDX.Multimedia.WaveFormat(sampleRate, bitsPerSample, channelCount); // Create and set the buffer description. var desc = new SoundBufferDescription(); desc.Format = format; desc.Flags = // Play sound even if application loses focus. BufferFlags.GlobalFocus | // This has to be true to use effects. BufferFlags.ControlEffects; desc.BufferBytes = wavBytes.Length; // Create and set the buffer for playing the sound. ApplicationBuffer = new SecondarySoundBuffer(ApplicationDevice, desc); ApplicationBuffer.Write(wavBytes, 0, LockFlags.None); var duration = AudioHelper.GetDuration(wavBytes.Length, sampleRate, bitsPerSample, channelCount); return(duration); }
/// <summary> /// Convert XML to WAV bytes. WAV won't have the header, so you have to add it separately. /// </summary> static void ConvertXmlToWav(PlayItem item) { var query = System.Web.HttpUtility.ParseQueryString(item.VoiceSourceKeys ?? ""); // Default is local. var source = VoiceSource.Local; Enum.TryParse(query[InstalledVoiceEx._KeySource], true, out source); var voiceId = query[InstalledVoiceEx._KeyVoiceId]; byte[] wavBytes; if (source == VoiceSource.Amazon) { var region = query[InstalledVoiceEx._KeyRegion]; var engine = query[InstalledVoiceEx._KeyEngine]; var client = new Voices.AmazonPolly( SettingsManager.Options.AmazonAccessKey, SettingsManager.Options.AmazonSecretKey, region ); wavBytes = client.SynthesizeSpeech(voiceId, item.Xml, Amazon.Polly.OutputFormat.Mp3, engine); if (wavBytes != null && wavBytes.Length > 0) { var pi = DecodeToPlayItem(wavBytes); item.WavHead = pi.WavHead; item.WavData = pi.WavData; item.Duration = pi.Duration; pi.Dispose(); } } else { wavBytes = ConvertSsmlXmlToWav(voiceId, item.Xml, item.WavHead); if (wavBytes != null && wavBytes.Length > 0) { item.WavData = wavBytes; item.Duration = AudioHelper.GetDuration(wavBytes.Length, item.WavHead.SampleRate, item.WavHead.BitsPerSample, item.WavHead.Channels); } } }
// Returns left and right float arrays. 'right' will be null if sound is mono. public static void GetWaveData(byte[] wav, int channels, int sampleRate, int bitsPerSample, out float[] left, out float[] right) { int bytesPerSample = bitsPerSample / 8; int samples = wav.Length / bytesPerSample / channels; // Allocate memory (right will be null if only mono sound) left = new float[samples]; right = (channels == 2) ? new float[samples] : null; // Write to float array/s: var subchunk2Size = wav.Length; var pos = 0; int i = 0; while (pos < subchunk2Size) { if (bytesPerSample == 1) { left[i] = AudioHelper.BytesToNormalized_8(wav[pos]); pos += 1; if (channels == 2) { right[i] = AudioHelper.BytesToNormalized_8(wav[pos]); pos += 1; } i++; } else { left[i] = AudioHelper.BytesToNormalized_16(wav[pos], wav[pos + 1]); pos += 2; if (channels == 2) { right[i] = AudioHelper.BytesToNormalized_16(wav[pos], wav[pos + 1]); pos += 2; } i++; } } }
// Return byte data from left and right float data. Ignore right when sound is mono public static void GetWaveData(float[] left, float[] right, ref byte[] data, int bitsPerSample) { int bytesPerSample = bitsPerSample / 8; // Calculate k // This value will be used to convert float to Int16 // We are not using Int16.Max to avoid peaks due to overflow conversions float k = (bytesPerSample == 1) ? (float)sbyte.MaxValue / left.Select(x => Math.Abs(x)).Max() : (float)short.MaxValue / left.Select(x => Math.Abs(x)).Max(); // Revert data to byte format Array.Clear(data, 0, data.Length); int dataLenght = left.Length; using (BinaryWriter writer = new BinaryWriter(new MemoryStream(data))) { for (int i = 0; i < dataLenght; i++) { if (bytesPerSample == 1) { AudioHelper.Write8bit(writer, left[i], k); if (right != null) { AudioHelper.Write8bit(writer, right[i], k); } } else { AudioHelper.Write16bit(writer, left[i], k); if (right != null) { AudioHelper.Write16bit(writer, right[i], k); } } } } }
/// <summary> /// Thread which will process all play items and convert XML to WAV bytes. /// </summary> /// <param name="status"></param> static void ProcessPlayItems(object status) { while (true) { PlayItem item = null; lock (threadIsRunningLock) { lock (playlistLock) { // Get first incomplete item in the list. JobStatusType[] validStates = { JobStatusType.Parsed, JobStatusType.Synthesized }; item = playlist.FirstOrDefault(x => validStates.Contains(x.Status)); // If nothing to do then... if (item == null || playlist.Any(x => x.Status == JobStatusType.Error)) { // Exit thread. threadIsRunning = false; return; } } } try { // If XML is available. if (item.Status == JobStatusType.Parsed) { item.Status = JobStatusType.Synthesizing; var encoding = System.Text.Encoding.UTF8; var synthesize = true; FileInfo xmlFi = null; FileInfo wavFi = null; if (SettingsManager.Options.CacheDataRead) { var dir = MainHelper.GetCreateCacheFolder(); // Look for generalized file first. var uniqueName = item.GetUniqueFilePath(true); // Get XML file path. var xmlFile = string.Format("{0}.xml", uniqueName); var xmlFullPath = Path.Combine(dir.FullName, xmlFile); xmlFi = new FileInfo(xmlFullPath); // If generalized file do not exists then... if (!xmlFi.Exists) { // Look for normal file. uniqueName = item.GetUniqueFilePath(false); // Get XML file path. xmlFile = string.Format("{0}.xml", uniqueName); xmlFullPath = Path.Combine(dir.FullName, xmlFile); xmlFi = new FileInfo(xmlFullPath); } // Prefer MP3 audio file first (custom recorded file). var wavFile = string.Format("{0}.mp3", uniqueName); var wavFullPath = Path.Combine(dir.FullName, wavFile); wavFi = new FileInfo(wavFullPath); // If wav do not exist or file is invalid. if (!wavFi.Exists || wavFi.Length == 0) { // Get WAV file path. wavFile = string.Format("{0}.wav", uniqueName); wavFullPath = Path.Combine(dir.FullName, wavFile); wavFi = new FileInfo(wavFullPath); } // If both files exists and Wav file is valid then... if (xmlFi.Exists && wavFi.Exists && wavFi.Length > 0) { using (Stream stream = new FileStream(wavFi.FullName, FileMode.Open, FileAccess.Read)) { // Load existing XML and WAV data into PlayItem. var ms = new MemoryStream(); var ad = new SharpDX.MediaFoundation.AudioDecoder(stream); var samples = ad.GetSamples(); var enumerator = samples.GetEnumerator(); while (enumerator.MoveNext()) { var sample = enumerator.Current.ToArray(); ms.Write(sample, 0, sample.Length); } // Read WAV head. item.WavHead = ad.WaveFormat; // Read WAV data. item.WavData = ms.ToArray(); item.Duration = (int)ad.Duration.TotalMilliseconds; } // Load XML. item.Xml = System.IO.File.ReadAllText(xmlFi.FullName); // Make sure WAV data is not synthesized. synthesize = false; } } if (synthesize) { item.WavHead = new SharpDX.Multimedia.WaveFormat( SettingsManager.Options.AudioSampleRate, SettingsManager.Options.AudioBitsPerSample, (int)SettingsManager.Options.AudioChannels); // WavHead could change. ConvertXmlToWav(item); } if (item.WavData != null) { var applyRate = SettingsManager.Options.ModifyLocallyRate && _Rate != 0; var applyPitch = SettingsManager.Options.ModifyLocallyPitch && _Pitch != 0; var applyVolume = SettingsManager.Options.ModifyLocallyVolume && item.Volume < 100; if (applyRate || applyPitch) { var parameters = new SoundStretch.RunParameters(); parameters.TempoDelta = GetTempoDeltaFromRate(_Rate); parameters.PitchDelta = (float)_Pitch; parameters.Speech = true; var inStream = new MemoryStream(); AudioHelper.Write(item, inStream); inStream.Position = 0; var outStream = new MemoryStream(); SoundStretch.SoundTouchHelper.Process(inStream, outStream, parameters); var outBytes = outStream.ToArray(); var pi = DecodeToPlayItem(outBytes); item.WavHead = pi.WavHead; item.WavData = pi.WavData; item.Duration = pi.Duration; pi.Dispose(); inStream.Dispose(); outStream.Dispose(); } if (applyVolume) { var inStream = new MemoryStream(); AudioHelper.Write(item, inStream); inStream.Position = 0; var vol = (float)item.Volume / 100f; var outStream = new MemoryStream(); AudioHelper.ChangeVolume(vol, inStream, outStream); var outBytes = outStream.ToArray(); var pi = DecodeToPlayItem(outBytes); item.WavHead = pi.WavHead; item.WavData = pi.WavData; item.Duration = pi.Duration; pi.Dispose(); inStream.Dispose(); outStream.Dispose(); } if (SettingsManager.Options.CacheDataWrite || SettingsManager.Options.CacheAudioConvert) { // Create directory if not exists. if (!xmlFi.Directory.Exists) { xmlFi.Directory.Create(); } if (!xmlFi.Exists) { // Write XML. System.IO.File.WriteAllText(xmlFi.FullName, item.Xml, encoding); } } // If data was synthesized i.e. was not loaded from the file then... if (synthesize && SettingsManager.Options.CacheDataWrite) { AudioHelper.Write(item, wavFi); } // If must convert data to other formats. if (SettingsManager.Options.CacheAudioConvert) { AudioHelper.Convert(item, wavFi); } } item.Status = (item.WavHead == null || item.WavData == null) ? item.Status = JobStatusType.Error : item.Status = JobStatusType.Synthesized; } if (item.Status == JobStatusType.Synthesized) { item.Status = JobStatusType.Pitching; ApplyPitch(item); item.Status = JobStatusType.Pitched; } } catch (Exception ex) { OnEvent(Exception, ex); item.Status = JobStatusType.Error; // Exit thread. threadIsRunning = false; return; } } }