From 1b0a5d5cb6766c0e6f957b3cc6a6b38ac8e5251d Mon Sep 17 00:00:00 2001 From: sim1222 Date: Sun, 19 Nov 2023 01:04:54 +0900 Subject: [PATCH] wip --- Elementary/Audio/AudioConverter.cs | 2 +- Elementary/Audio/AudioManager.cs | 10 +- Elementary/Audio/AudioMixer.cs | 152 ++++++++++---------- Elementary/Audio/IAudioConverter.cs | 7 +- Elementary/Audio/IAudioManager.cs | 6 + Elementary/Audio/IPlaybackQueue.cs | 8 ++ Elementary/Audio/ITtsAPI.cs | 6 + Elementary/Audio/MessageHandler.cs | 30 ---- Elementary/Audio/PlaybackQueue.cs | 44 +++--- Elementary/Audio/SozaiAPI.cs | 4 +- Elementary/Audio/VoicevoxAPI.cs | 18 ++- Elementary/Commands/MessageCommands.cs | 28 +++- Elementary/Elementary.csproj | 10 ++ Elementary/{Commands => Message}/Handler.cs | 3 +- Elementary/Message/MessageHandler.cs | 52 +++++++ Elementary/Program.cs | 27 ++-- 16 files changed, 250 insertions(+), 157 deletions(-) create mode 100644 Elementary/Audio/IAudioManager.cs create mode 100644 Elementary/Audio/IPlaybackQueue.cs create mode 100644 Elementary/Audio/ITtsAPI.cs delete mode 100644 Elementary/Audio/MessageHandler.cs rename Elementary/{Commands => Message}/Handler.cs (97%) create mode 100644 Elementary/Message/MessageHandler.cs diff --git a/Elementary/Audio/AudioConverter.cs b/Elementary/Audio/AudioConverter.cs index 99cb0e3..0316e20 100644 --- a/Elementary/Audio/AudioConverter.cs +++ b/Elementary/Audio/AudioConverter.cs @@ -6,7 +6,7 @@ using NAudio.Wave; namespace Elementary.Audio; -public class AudioConverter +public class AudioConverter : IAudioConverter { public readonly WaveFormat WaveFormat = new(48000, 16, 2); diff --git a/Elementary/Audio/AudioManager.cs b/Elementary/Audio/AudioManager.cs index 2f21483..4716bd7 100644 --- a/Elementary/Audio/AudioManager.cs +++ b/Elementary/Audio/AudioManager.cs @@ -62,7 +62,13 @@ public class AudioManager isConnected = false; await JoinChannel(channel); }; - + + _audioClient.StreamCreated += async (id, stream) => + { + _logger.Log(LogLevel.Info, $"Stream created {id}"); + await Task.Delay(-1); + }; + _audioStream = _audioClient.CreatePCMStream(AudioApplication.Music, 128 * 1024); _playbackQueue = _services.GetRequiredService(); @@ -126,7 +132,7 @@ public class AudioManager text = text.Replace("~", "ー"); - float volume = 0.12f; + float volume = 0.1f; Stream? stream = await _sozaiAPI.GetAudioStream(text); if (stream == null) diff --git a/Elementary/Audio/AudioMixer.cs b/Elementary/Audio/AudioMixer.cs index d418b9c..4d85d6b 100644 --- a/Elementary/Audio/AudioMixer.cs +++ b/Elementary/Audio/AudioMixer.cs @@ -1,80 +1,72 @@ -// using ManagedBass; -// using ManagedBass.Mix; -// -// namespace Elementary.Audio; -// -// public class AudioMixer -// { -// private readonly int _mixerStream; -// private MemoryStream?[] _inputStreams; -// private Stream _outStream; -// private object _lock = new(); -// -// public AudioMixer(Stream outStream) -// { -// _outStream = outStream; -// Bass.Init(-1, 44100, DeviceInitFlags.NoSpeakerAssignment, IntPtr.Zero); -// _mixerStream = BassMix.CreateMixerStream(44100, 2, BassFlags.Float | BassFlags.MixerNonStop); -// _inputStreams = new MemoryStream[10]; -// -// Bass.ChannelPlay(_mixerStream); -// } -// -// /// -// /// Add a stream to the mixer. -// /// wait for the stream to finish playing -// /// -// /// -// /// -// public void AddStream(Stream stream, float volume = 1.0f) -// { -// Console.WriteLine("Adding stream to mixer"); -// byte[] pcmBytes; -// using (MemoryStream ms = new()) -// { -// stream.CopyTo(ms); -// pcmBytes = ms.ToArray(); -// } -// -// lock (_lock) -// { -// for (var i = 0; i < _inputStreams.Length; i++) -// { -// if (_inputStreams[i] == null) -// { -// _inputStreams[i] = new MemoryStream(pcmBytes); -// break; -// } -// } -// } -// -// var channel = Bass.CreateStream(pcmBytes, 0, pcmBytes.Length, BassFlags.Float); -// Bass.ChannelSetAttribute(channel, ChannelAttribute.Volume, volume); -// BassMix.MixerAddChannel(_mixerStream, channel, BassFlags.Default); -// -// int length = Bass.ChannelGetData(_mixerStream, new byte[4096], 4096); -// byte[] buffer = new byte[length]; -// length = Bass.ChannelGetData(_mixerStream, buffer, length); -// _outStream.Write(buffer, 0, length); -// } -// -// public void Stop() -// { -// Bass.ChannelStop(_mixerStream); -// } -// -// public void Dispose() -// { -// lock (_lock) -// { -// foreach (var stream in _inputStreams) -// { -// stream?.Dispose(); -// } -// } -// -// _outStream.Flush(); -// Bass.StreamFree(_mixerStream); -// Bass.Free(); -// } -// } \ No newline at end of file +using CSCore; +using CSCore.Codecs.WAV; +using System.Collections.Generic; +using System.IO; +using WaveFormat = CSCore.WaveFormat; + +public class StreamMixer +{ + private List sources = new List(); + private WaveWriter writer; + + public void AddInputStream(Stream inputStream) + { + var waveSource = new WaveStream(inputStream, new WaveFormat(48000, 16, 2)); + lock (sources) + { + sources.Add(waveSource); + } + } + + public void WriteMixedToOutputStream(Stream outputStream) + { + WaveFormat format = new WaveFormat(48000, 16, 2); + writer = new WaveWriter(outputStream, format); + + byte[] buffer = new byte[format.BytesPerSecond / 2]; + + while (sources.Count > 0) + { + int read = 0; + lock (sources) + { + foreach (var source in sources) + { + int bytes = source.Read(buffer, 0, buffer.Length); + if (bytes == 0) + { + source.Dispose(); + sources.Remove(source); + } + else + { + read = bytes; + } + } + } + + if (read > 0) + { + writer.Write(buffer, 0, read); + } + } + + writer.Dispose(); + } + + public void Stop() + { + writer?.Dispose(); + writer = null; + + lock (sources) + { + foreach (var source in sources) + { + source.Dispose(); + } + + sources.Clear(); + } + } +} \ No newline at end of file diff --git a/Elementary/Audio/IAudioConverter.cs b/Elementary/Audio/IAudioConverter.cs index d28e210..3339319 100644 --- a/Elementary/Audio/IAudioConverter.cs +++ b/Elementary/Audio/IAudioConverter.cs @@ -1,10 +1,13 @@ using System.Diagnostics; +using NAudio.Wave; namespace Elementary.Audio; public interface IAudioConverter { - public Process CreateStreamFromFilePath(string path); + WaveFormatConversionStream CreateStreamFromFilePath(string path, float volume = 1.0f); - public Process CreateStreamFromStream(Stream stream); + WaveFormatConversionStream CreateStreamFromStream(Stream stream); + + WaveFormatConversionStream CreateStreamFromStream(Stream stream, float volume = 1.0f); } \ No newline at end of file diff --git a/Elementary/Audio/IAudioManager.cs b/Elementary/Audio/IAudioManager.cs new file mode 100644 index 0000000..46ee741 --- /dev/null +++ b/Elementary/Audio/IAudioManager.cs @@ -0,0 +1,6 @@ +namespace Elementary.Audio; + +public interface IAudioManager +{ + +} \ No newline at end of file diff --git a/Elementary/Audio/IPlaybackQueue.cs b/Elementary/Audio/IPlaybackQueue.cs new file mode 100644 index 0000000..9bf405e --- /dev/null +++ b/Elementary/Audio/IPlaybackQueue.cs @@ -0,0 +1,8 @@ +namespace Elementary.Audio; + +public interface IPlaybackQueue +{ + Task Enqueue(PlaybackJob job); + + void Flush(); +} \ No newline at end of file diff --git a/Elementary/Audio/ITtsAPI.cs b/Elementary/Audio/ITtsAPI.cs new file mode 100644 index 0000000..9351f33 --- /dev/null +++ b/Elementary/Audio/ITtsAPI.cs @@ -0,0 +1,6 @@ +namespace Elementary.Audio; + +public interface ITtsAPI +{ + Task GetAudioStream(string text); +} \ No newline at end of file diff --git a/Elementary/Audio/MessageHandler.cs b/Elementary/Audio/MessageHandler.cs deleted file mode 100644 index ed3f2e6..0000000 --- a/Elementary/Audio/MessageHandler.cs +++ /dev/null @@ -1,30 +0,0 @@ -using Discord.Audio; -using Discord.WebSocket; - -namespace Elementary.Audio; - -public class MessageHandler -{ - // private IAudioClient _audioClient; - private PlaybackQueue _playbackQueue; - - public MessageHandler(PlaybackQueue playbackQueue) - { - _playbackQueue = playbackQueue; - } - - - public async Task HandleMessage(SocketMessage message) - { - List lines = message.Content.Split("\n").ToList(); - - foreach (var line in lines) - { - await _playbackQueue.Enqueue(new PlaybackJob() - { - Type = JobType.Text, - Text = line - }); - } - } -} \ No newline at end of file diff --git a/Elementary/Audio/PlaybackQueue.cs b/Elementary/Audio/PlaybackQueue.cs index 06359e8..c4267d2 100644 --- a/Elementary/Audio/PlaybackQueue.cs +++ b/Elementary/Audio/PlaybackQueue.cs @@ -2,6 +2,7 @@ using Discord.Audio; using NAudio.Wave; using NLog; +using Exception = System.Exception; namespace Elementary.Audio; @@ -19,7 +20,7 @@ public class PlaybackJob public float Volume = 1.0f; } -public class PlaybackQueue +public class PlaybackQueue : IPlaybackQueue { // any type of job ConcurrentQueue @@ -73,26 +74,35 @@ public class PlaybackQueue private async void PlayNext() { - if (_queue.TryDequeue(out var currentStream)) + try { - _logger.Info("Start Playing"); - - _isPlaying = true; - await (currentStream.Type switch + if (_queue.TryDequeue(out var currentStream)) { - JobType.Audio => _audioManager.PlayAudio(currentStream.Text), - JobType.Text => _audioManager.PlayText(currentStream.Text), - _ => throw new ArgumentOutOfRangeException() - }); - _logger.Info("Finished Playing"); - await Task.Delay(200); + _logger.Info("Start Playing"); + + _isPlaying = true; + await (currentStream.Type switch + { + JobType.Audio => _audioManager.PlayAudio(currentStream.Text), + JobType.Text => _audioManager.PlayText(currentStream.Text), + _ => throw new ArgumentOutOfRangeException() + }); + _logger.Info("Finished Playing"); + await Task.Delay(200); + _isPlaying = false; + PlayNext(); + } + else if (_queue.IsEmpty) + { + _logger.Info("Queue is empty"); + _isPlaying = false; + } + } + catch (Exception e) + { + _logger.Error(e); _isPlaying = false; PlayNext(); } - else if (_queue.IsEmpty) - { - _logger.Info("Queue is empty"); - _isPlaying = false; - } } } \ No newline at end of file diff --git a/Elementary/Audio/SozaiAPI.cs b/Elementary/Audio/SozaiAPI.cs index 1f9268d..25c9cc1 100644 --- a/Elementary/Audio/SozaiAPI.cs +++ b/Elementary/Audio/SozaiAPI.cs @@ -4,7 +4,7 @@ using NLog; namespace Elementary.Audio; -public class SozaiAPI +public class SozaiAPI : ITtsAPI { private ILogger _logger; @@ -30,13 +30,13 @@ public class SozaiAPI public SozaiAPI() { - _client = new HttpClient(); _logger = LogManager.GetCurrentClassLogger(); _cache = new MemoryCache(new MemoryCacheOptions()); } public async Task Setup(string url) { + _client = new HttpClient(); var response = await _client.GetAsync(url); var stream = await response.Content.ReadAsStreamAsync(); Assets = await JsonSerializer.DeserializeAsync(stream); diff --git a/Elementary/Audio/VoicevoxAPI.cs b/Elementary/Audio/VoicevoxAPI.cs index 6674231..5972189 100644 --- a/Elementary/Audio/VoicevoxAPI.cs +++ b/Elementary/Audio/VoicevoxAPI.cs @@ -12,14 +12,18 @@ public class VoicevoxAPI private ILogger _logger; private MemoryCache _cache; + public VoicevoxAPI() + { + _logger = LogManager.GetCurrentClassLogger(); + _cache = new MemoryCache(new MemoryCacheOptions()); + } + public async Task Setup(string url) { + _client = new HttpClient(); Uri _url = new(url); _APIRootUrl = new UriBuilder($"{_url.Scheme}://{_url.Host}:{_url.Port}"); - _client = new HttpClient(); - _logger = LogManager.GetCurrentClassLogger(); - _cache = new MemoryCache(new MemoryCacheOptions()); } /// @@ -31,7 +35,7 @@ public class VoicevoxAPI public async Task Speak(string text, string speaker = "47") { _logger.Info($"Requested TTS {text}"); - + if (_cache.TryGetValue(text, out Stream? stream)) { _logger.Info($"Cache hit {text}"); @@ -52,9 +56,9 @@ public class VoicevoxAPI builder.Path = "/audio_query"; builder.Query = $"text={text}&speaker={speaker}"; - var response = await _client.PostAsync(builder.Uri, new StringContent(string.Empty)); - var stream = await response.Content.ReadAsStreamAsync(); - return await new StreamReader(stream).ReadToEndAsync(); + var response = await _client.PostAsync(builder.Uri, new StringContent(string.Empty)); + var stream = await response.Content.ReadAsStreamAsync(); + return await new StreamReader(stream).ReadToEndAsync(); } internal async Task GetAudioStream(string query, string speaker = "1") diff --git a/Elementary/Commands/MessageCommands.cs b/Elementary/Commands/MessageCommands.cs index d10f65a..f5c1a8a 100644 --- a/Elementary/Commands/MessageCommands.cs +++ b/Elementary/Commands/MessageCommands.cs @@ -1,4 +1,6 @@ -using Discord; +using System.Diagnostics; +using System.Reflection; +using Discord; using Discord.Commands; using Elementary.Audio; using Elementary.Dictionary; @@ -119,4 +121,28 @@ public class MessageCommands : ModuleBase { await _audioManager.PlayYoutube(url); } + + [Command("memory", RunMode = RunMode.Async)] + [Summary("Show memory usage.")] + public async Task MemoryAsync() + { + var memory = Environment.WorkingSet; + await ReplyAsync($"Memory: {memory / 1024 / 1024} MB"); + } + + [Command("restart", RunMode = RunMode.Async)] + [Summary("Restart the bot.")] + public async Task RestartAsync() + { + await ReplyAsync("Restarting..."); + await LeaveAsync(); + var assembly = Assembly.GetEntryAssembly(); + var path = assembly.Location; + path = path.Replace(".dll", ".exe"); + Console.WriteLine(path); + Process.Start(path); + + Environment.Exit(0); + + } } \ No newline at end of file diff --git a/Elementary/Elementary.csproj b/Elementary/Elementary.csproj index e9ecbd4..a0988bb 100644 --- a/Elementary/Elementary.csproj +++ b/Elementary/Elementary.csproj @@ -14,9 +14,13 @@ .dockerignore + + Always + + @@ -57,4 +61,10 @@ + + + + + + diff --git a/Elementary/Commands/Handler.cs b/Elementary/Message/Handler.cs similarity index 97% rename from Elementary/Commands/Handler.cs rename to Elementary/Message/Handler.cs index e0cbf40..d01db69 100644 --- a/Elementary/Commands/Handler.cs +++ b/Elementary/Message/Handler.cs @@ -1,11 +1,10 @@ using System.Reflection; -using Discord.Audio; using Discord.Commands; using Discord.WebSocket; using Elementary.Audio; using NLog; -namespace Elementary.Commands; +namespace Elementary.Message; public class Handler { diff --git a/Elementary/Message/MessageHandler.cs b/Elementary/Message/MessageHandler.cs new file mode 100644 index 0000000..09f7ff8 --- /dev/null +++ b/Elementary/Message/MessageHandler.cs @@ -0,0 +1,52 @@ +using Discord.WebSocket; +using Elementary.Audio; + +namespace Elementary.Message; + +public class MessageHandler +{ + // private IAudioClient _audioClient; + private PlaybackQueue _playbackQueue; + + public MessageHandler(PlaybackQueue playbackQueue) + { + _playbackQueue = playbackQueue; + } + + + public async Task HandleMessage(SocketMessage message) + { + // if contains mention, add mentioned user name to lines and replace + List lines = new(); + + if (message.MentionedUsers.Count > 0) + { + foreach (var mentionedUser in message.MentionedUsers) + { + message.Content.Replace(mentionedUser.Mention, $"@{mentionedUser.GlobalName}").Split('\n').ToList() + .ForEach(lines.Add); + } + } + else + { + if (message.Content.Contains("```")) + { + lines = new List {"コードブロック"}; + } + else + { + message.Content.Split('\n').ToList().ForEach(lines.Add); + } + } + + + foreach (var line in lines) + { + await _playbackQueue.Enqueue(new PlaybackJob() + { + Type = JobType.Text, + Text = line + }); + } + } +} \ No newline at end of file diff --git a/Elementary/Program.cs b/Elementary/Program.cs index fd04652..ff26748 100644 --- a/Elementary/Program.cs +++ b/Elementary/Program.cs @@ -7,6 +7,7 @@ using Discord.WebSocket; using Elementary.Audio; using Elementary.Commands; using Elementary.Dictionary; +using Elementary.Message; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using NAudio.Wave; @@ -32,14 +33,13 @@ public class Program private Mecab _mecab; private Ytdlp _ytdlp; - public static async Task Main(string[] args) + public static void Main(string[] args) { while (true) { try { - await new Program().Setup(); - await Task.Delay(-1); + new Program().Setup().Wait(); } catch (Exception e) { @@ -55,7 +55,6 @@ public class Program { GatewayIntents = GatewayIntents.AllUnprivileged | GatewayIntents.MessageContent }); - _commands = new CommandService(); _services = new ServiceCollection() .AddLogging(builder => { @@ -64,21 +63,23 @@ public class Program builder.AddNLog(configuration.ConfigurationRoot); }) .AddSingleton(_client) - .AddSingleton(_commands) - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddScoped() + .AddTransient() + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped() .AddTransient() - .AddSingleton() + .AddScoped() .AddSingleton() .AddSingleton() - .AddSingleton() - .AddSingleton() + .AddScoped() + .AddScoped() .BuildServiceProvider(); _logger = LogManager.GetCurrentClassLogger(); + + _commands = _services.GetRequiredService(); _sozaiAPI = _services.GetRequiredService(); _voicevoxAPI = _services.GetRequiredService();