wip
This commit is contained in:
parent
7d60313cf7
commit
1b0a5d5cb6
@ -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);
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
6
Elementary/Audio/IAudioManager.cs
Normal file
6
Elementary/Audio/IAudioManager.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Elementary.Audio;
|
||||
|
||||
public interface IAudioManager
|
||||
{
|
||||
|
||||
}
|
8
Elementary/Audio/IPlaybackQueue.cs
Normal file
8
Elementary/Audio/IPlaybackQueue.cs
Normal file
@ -0,0 +1,8 @@
|
||||
namespace Elementary.Audio;
|
||||
|
||||
public interface IPlaybackQueue
|
||||
{
|
||||
Task Enqueue(PlaybackJob job);
|
||||
|
||||
void Flush();
|
||||
}
|
6
Elementary/Audio/ITtsAPI.cs
Normal file
6
Elementary/Audio/ITtsAPI.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Elementary.Audio;
|
||||
|
||||
public interface ITtsAPI
|
||||
{
|
||||
Task<Stream?> GetAudioStream(string text);
|
||||
}
|
@ -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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
|
||||
}
|
||||
}
|
@ -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>
|
||||
|
@ -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
|
||||
{
|
52
Elementary/Message/MessageHandler.cs
Normal file
52
Elementary/Message/MessageHandler.cs
Normal 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
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -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>();
|
||||
|
Loading…
x
Reference in New Issue
Block a user