public static PlayItem DecodeToPlayItem(byte[] audioFileBytes) { var item = new PlayItem(); // Audio file bytes into memory stream. using (var stream = new MemoryStream(audioFileBytes)) { // Load existing XML and WAV data into PlayItem. using (var ms = new MemoryStream()) { // Loading stream into decoder. using (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; } } } item.Status = JobStatusType.Synthesized; return(item); }
public static void AddMessageToPlay(string text) { if (string.IsNullOrEmpty(text)) { return; } if (text.StartsWith("<message")) { // Insert name="Undefined" attribute if not submited. text = !text.Contains("name=") ? text.Replace("<message", "<message name=\"Undefined\"") : text; var voiceItem = (VoiceListItem)Activator.CreateInstance(Program.MonitorItem.GetType()); voiceItem.Load(text); addVoiceListItem(voiceItem); } else { var item = new PlayItem() { Text = "SAPI XML", Xml = text, Status = JobStatusType.Parsed, }; lock (playlistLock) { playlist.Add(item); } } }
public static void AddIntroSoundToPlayList(string text, string group, Stream stream) { var item = new PlayItem() { Text = text, WavData = new byte[0], StreamData = stream, Group = group, Status = JobStatusType.Pitched, }; lock (playlistLock) { playlist.Add(item); } }
public static void Write(PlayItem item, FileInfo wavFi) { // Create directory if not exists. if (!wavFi.Directory.Exists) { wavFi.Directory.Create(); } if (item == null) { throw new ArgumentNullException(nameof(item)); } if (wavFi == null) { throw new ArgumentNullException(nameof(wavFi)); } using (var stream = new FileStream(wavFi.FullName, FileMode.Create)) Write(item, stream); }
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> /// 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); } } }
public static void Write(PlayItem item, Stream stream) { if (item == null) { throw new ArgumentNullException(nameof(item)); } if (stream == null) { throw new ArgumentNullException(nameof(stream)); } var headBytes = GetWavHead( item.WavData.Length, item.WavHead.SampleRate, item.WavHead.BitsPerSample, item.WavHead.Channels ); // Write WAV head. stream.Write(headBytes, 0, headBytes.Length); // Write WAV data. stream.Write(item.WavData, 0, item.WavData.Length); }
public static void AddMessageToPlay(string text) { if (!string.IsNullOrEmpty(text)) { if (text.StartsWith("<message")) { var voiceItem = (VoiceListItem)Activator.CreateInstance(Program.MonitorItem.GetType()); voiceItem.Load(text); addVoiceListItem(voiceItem); } else { var item = new PlayItem() { Text = "SAPI XML", Xml = text, Status = JobStatusType.Parsed, }; lock (playlistLock) { playlist.Add(item); } } } }
/// <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; } } }
public static List <PlayItem> AddTextToPlaylist(string game, string text, bool addToPlaylist, string voiceGroup, // Optional properties for NPC character. string name = null, string gender = null, string effect = null, int volume = 100, // Optional propertied for player character string playerName = null, string playerNameChanged = null, string playerClass = null, // Keys which will be used to which voice source (Local, Amazon or Google). string voiceSourceKeys = null ) { MessageGender _gender; Enum.TryParse(gender, out _gender); // It will take too long to convert large amount of text to WAV data and apply all filters. // This function will split text into smaller sentences. var cs = "[comment]"; var ce = "[/comment]"; var items = new List <PlayItem>(); // Split sentences if option is enabled. var splitItems = SettingsManager.Options.SplitMessageIntoSentences ? MainHelper.SplitText(text, new string[] { ". ", "! ", "? ", cs, ce }) : MainHelper.SplitText(text, new string[] { cs, ce }); var sentences = splitItems.Where(x => (x.Value + x.Key).Trim().Length > 0).ToArray(); bool comment = false; // Loop trough each sentence. for (int i = 0; i < sentences.Length; i++) { var block = sentences[i]; // Combine sentence and separator. var sentence = block.Value + block.Key.Replace(cs, "").Replace(ce, "") + ""; if (!string.IsNullOrEmpty(sentence.Trim('\r', '\n', ' '))) { var item = new PlayItem(); item.Game = game; // Set Player properties item.PlayerName = playerName; item.PlayerNameChanged = playerNameChanged; item.PlayerClass = playerClass; // Set NPC properties. item.Name = name; item.Gender = _gender; item.Effect = effect; item.Volume = volume; // Set data properties. item.Status = JobStatusType.Parsed; item.IsComment = comment; item.Group = voiceGroup; item.VoiceSourceKeys = voiceSourceKeys; item.Text = sentence; if (SettingsManager.Options.CacheDataGeneralize) { item.Text = item.GetGeneralizedText(); } item.Xml = ConvertTextToXml(item.Text, comment, volume); items.Add(item); if (addToPlaylist) { lock (playlistLock) { playlist.Add(item); } } } ; // If comment started. if (block.Key == cs) { comment = true; } // If comment ended. if (block.Key == ce) { comment = false; } } return(items); }
public static void Convert(PlayItem item, FileInfo wavFi) { if (wavFi == null) { throw new ArgumentNullException(nameof(wavFi)); } var convertFormat = SettingsManager.Options.CacheAudioFormat; var fileName = Path.GetFileNameWithoutExtension(wavFi.FullName); string fullName; if (convertFormat == CacheFileFormat.ULaw || convertFormat == CacheFileFormat.ALaw) { fullName = Path.Combine(wavFi.Directory.FullName, fileName + "." + convertFormat.ToString().ToLower() + ".wav"); } else if (convertFormat == CacheFileFormat.MP3) { fullName = Path.Combine(wavFi.Directory.FullName, fileName + ".copy.mp3"); } else if (convertFormat == CacheFileFormat.WAV) { fullName = Path.Combine(wavFi.Directory.FullName, fileName + ".copy.wav"); } else { // Do nothing if format do not match. return; } // Exit if file already exists. if (File.Exists(fullName)) { return; } // Write whole WAV (head and data) into one stream. var source = new MemoryStream(); Write(item, source); source.Position = 0; // Create directory if not exists. if (!wavFi.Directory.Exists) { wavFi.Directory.Create(); } // https://www.codeproject.com/Articles/501521/How-to-convert-between-most-audio-formats-in-NET source.Position = 0; var reader = new WaveFileReader(source); if (convertFormat == CacheFileFormat.ULaw || convertFormat == CacheFileFormat.ALaw) { // The ACM mu-law encoder expects its input to be 16 bit. // If you're working with mu or a-law, the sample rate is likely to be low as well. // The following two lines of code will create a zero-length stream of PCM 16 bit and // pass it into a WaveFormatConversionStream to convert it to a-law. // It should not throw a "conversion not possible" error unless for some reason you don't have the G.711 encoder installed on your machine. var wavFormat = convertFormat == CacheFileFormat.ULaw ? WaveFormatEncoding.MuLaw : WaveFormatEncoding.ALaw; var destinationFormat = WaveFormat.CreateCustomFormat( wavFormat, SettingsManager.Options.CacheAudioSampleRate, (int)SettingsManager.Options.CacheAudioChannels, SettingsManager.Options.CacheAudioAverageBitsPerSecond / 8, SettingsManager.Options.CacheAudioBlockAlign, SettingsManager.Options.CacheAudioBitsPerSample ); var conversionStream1 = new WaveFormatConversionStream(new WaveFormat(destinationFormat.SampleRate, 16, destinationFormat.Channels), reader); using (var conversionStream2 = new WaveFormatConversionStream(destinationFormat, conversionStream1)) WaveFileWriter.CreateWaveFile(fullName, conversionStream2); conversionStream1.Dispose(); } else if (convertFormat == CacheFileFormat.MP3) { // If media foundation is not started then you will get this exception during MP3 encoding: // System.Runtime.InteropServices.COMException: // 'The request is invalid because Shutdown() has been called. (Exception from HRESULT: 0xC00D3E85)' MediaFoundationApi.Startup(); MediaFoundationEncoder.EncodeToMp3(reader, fullName, SettingsManager.Options.CacheAudioAverageBitsPerSecond); MediaFoundationApi.Shutdown(); } else if (convertFormat == CacheFileFormat.WAV) { var destWav = new WaveFormat( SettingsManager.Options.CacheAudioSampleRate, SettingsManager.Options.CacheAudioBitsPerSample, (int)SettingsManager.Options.CacheAudioChannels ); using (var conversionStream2 = new WaveFormatConversionStream(destWav, reader)) WaveFileWriter.CreateWaveFile(fullName, conversionStream2); } reader.Dispose(); }