This commit is contained in:
sim1222 2023-11-19 01:04:54 +09:00
parent 7d60313cf7
commit 1b0a5d5cb6
16 changed files with 250 additions and 157 deletions

View File

@ -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);

View File

@ -63,6 +63,12 @@ public class AudioManager
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<PlaybackQueue>();
@ -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)

View File

@ -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);
// }
//
// /// <summary>
// /// Add a stream to the mixer.
// /// wait for the stream to finish playing
// /// </summary>
// /// <param name="stream"></param>
// /// <param name="volume"></param>
// 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();
// }
// }
using CSCore;
using CSCore.Codecs.WAV;
using System.Collections.Generic;
using System.IO;
using WaveFormat = CSCore.WaveFormat;
public class StreamMixer
{
private List<IWaveSource> sources = new List<IWaveSource>();
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();
}
}
}

View File

@ -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);
}

View File

@ -0,0 +1,6 @@
namespace Elementary.Audio;
public interface IAudioManager
{
}

View File

@ -0,0 +1,8 @@
namespace Elementary.Audio;
public interface IPlaybackQueue
{
Task Enqueue(PlaybackJob job);
void Flush();
}

View File

@ -0,0 +1,6 @@
namespace Elementary.Audio;
public interface ITtsAPI
{
Task<Stream?> GetAudioStream(string text);
}

View File

@ -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<String> lines = message.Content.Split("\n").ToList();
foreach (var line in lines)
{
await _playbackQueue.Enqueue(new PlaybackJob()
{
Type = JobType.Text,
Text = line
});
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<Asset[]>(stream);

View File

@ -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());
}
/// <summary>
@ -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<Stream?> GetAudioStream(string query, string speaker = "1")

View File

@ -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<SocketCommandContext>
{
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);
}
}

View File

@ -14,9 +14,13 @@
<Content Include="..\.dockerignore">
<Link>.dockerignore</Link>
</Content>
<Content Include="emoji-ja\data\emoji_ja.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<PackageReference Include="CSCore" Version="1.2.1.2" />
<PackageReference Include="Discord.Net" Version="3.11.0"/>
<PackageReference Include="Discord.Net.Core" Version="3.11.0"/>
<PackageReference Include="LiteDB" Version="5.0.17" />
@ -57,4 +61,10 @@
</ItemGroup>
<ItemGroup>
<Compile Remove="Audio\AudioMixer.cs" />
</ItemGroup>
</Project>

View File

@ -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
{

View File

@ -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 <id>
List<String> 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<string> {"コードブロック"};
}
else
{
message.Content.Split('\n').ToList().ForEach(lines.Add);
}
}
foreach (var line in lines)
{
await _playbackQueue.Enqueue(new PlaybackJob()
{
Type = JobType.Text,
Text = line
});
}
}
}

View File

@ -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,22 +63,24 @@ public class Program
builder.AddNLog(configuration.ConfigurationRoot);
})
.AddSingleton(_client)
.AddSingleton(_commands)
.AddSingleton<Handler>()
.AddSingleton<AudioManager>()
.AddSingleton<VoicevoxAPI>()
.AddSingleton<SozaiAPI>()
.AddSingleton<PlaybackQueue>()
.AddScoped<CommandService>()
.AddTransient<Handler>()
.AddScoped<AudioManager>()
.AddScoped<PlaybackQueue>()
.AddScoped<VoicevoxAPI>()
.AddScoped<SozaiAPI>()
.AddTransient<AudioConverter>()
.AddSingleton<MessageHandler>()
.AddScoped<MessageHandler>()
.AddSingleton<EmojiDictionary>()
.AddSingleton<DictionaryDB>()
.AddSingleton<Mecab>()
.AddSingleton<Ytdlp>()
.AddScoped<Mecab>()
.AddScoped<Ytdlp>()
.BuildServiceProvider();
_logger = LogManager.GetCurrentClassLogger();
_commands = _services.GetRequiredService<CommandService>();
_sozaiAPI = _services.GetRequiredService<SozaiAPI>();
_voicevoxAPI = _services.GetRequiredService<VoicevoxAPI>();
_ytdlp = _services.GetRequiredService<Ytdlp>();