2016-01-26 20:18:48 +01:00
|
|
|
|
using System;
|
|
|
|
|
using System.Linq;
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
using Discord;
|
|
|
|
|
using Discord.Commands;
|
|
|
|
|
using Discord.Audio;
|
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Diagnostics;
|
|
|
|
|
using NadekoBot.Extensions;
|
2016-02-02 23:41:13 +01:00
|
|
|
|
using VideoLibrary;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
|
|
|
|
namespace NadekoBot.Classes.Music {
|
|
|
|
|
public enum StreamState {
|
|
|
|
|
Resolving,
|
|
|
|
|
Queued,
|
|
|
|
|
Buffering, //not using it atm
|
|
|
|
|
Playing,
|
|
|
|
|
Completed
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class StreamRequest {
|
|
|
|
|
public Server Server { get; }
|
|
|
|
|
public User User { get; }
|
|
|
|
|
public string Query { get; }
|
|
|
|
|
|
2016-01-29 08:23:15 +01:00
|
|
|
|
public string Title { get; internal set; } = String.Empty;
|
2016-02-06 14:43:03 +01:00
|
|
|
|
private string Provider { get; set; }
|
|
|
|
|
|
2016-02-14 23:48:07 +01:00
|
|
|
|
public string FullPrettyName => $"**【 {Title.TrimTo(55)} 】**`{(Provider == null ? "-" : Provider)}`";
|
2016-01-29 08:23:15 +01:00
|
|
|
|
|
2016-01-26 20:18:48 +01:00
|
|
|
|
private MusicStreamer musicStreamer = null;
|
2016-01-29 13:28:51 +01:00
|
|
|
|
public StreamState State => musicStreamer?.State ?? privateState;
|
|
|
|
|
private StreamState privateState = StreamState.Resolving;
|
|
|
|
|
|
|
|
|
|
public bool IsPaused => MusicControls.IsPaused;
|
|
|
|
|
|
2016-02-04 14:17:15 +01:00
|
|
|
|
public float Volume => MusicControls?.Volume ?? 1.0f;
|
|
|
|
|
|
2016-02-05 15:33:22 +01:00
|
|
|
|
public bool RadioLink { get; private set; }
|
|
|
|
|
|
2016-01-31 19:51:27 +01:00
|
|
|
|
public MusicControls MusicControls;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-02-05 15:33:22 +01:00
|
|
|
|
public StreamRequest(CommandEventArgs e, string query, MusicControls mc, bool radio = false) {
|
2016-01-26 20:18:48 +01:00
|
|
|
|
if (e == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(e));
|
|
|
|
|
if (query == null)
|
|
|
|
|
throw new ArgumentNullException(nameof(query));
|
2016-01-29 13:28:51 +01:00
|
|
|
|
this.MusicControls = mc;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
this.Server = e.Server;
|
|
|
|
|
this.Query = query;
|
2016-02-05 15:33:22 +01:00
|
|
|
|
this.RadioLink = radio;
|
2016-01-31 19:51:27 +01:00
|
|
|
|
mc.SongQueue.Add(this);
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-02-16 11:57:11 +01:00
|
|
|
|
public async Task Resolve() {
|
2016-02-04 18:24:06 +01:00
|
|
|
|
string uri = null;
|
2016-01-29 13:28:51 +01:00
|
|
|
|
try {
|
2016-02-05 15:33:22 +01:00
|
|
|
|
if (RadioLink) {
|
|
|
|
|
uri = Query;
|
2016-02-06 14:43:03 +01:00
|
|
|
|
Title = $"{Query}";
|
|
|
|
|
Provider = "Radio Stream";
|
2016-02-05 15:33:22 +01:00
|
|
|
|
}
|
|
|
|
|
else if (SoundCloud.Default.IsSoundCloudLink(Query)) {
|
|
|
|
|
if (OnResolving != null)
|
|
|
|
|
OnResolving();
|
2016-02-04 18:24:06 +01:00
|
|
|
|
var svideo = await SoundCloud.Default.GetVideoAsync(Query);
|
2016-02-06 14:43:03 +01:00
|
|
|
|
Title = svideo.FullName;
|
|
|
|
|
Provider = "SoundCloud";
|
2016-02-04 18:24:06 +01:00
|
|
|
|
uri = svideo.StreamLink;
|
|
|
|
|
Console.WriteLine(uri);
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
else {
|
2016-02-04 18:24:06 +01:00
|
|
|
|
|
|
|
|
|
if (OnResolving != null)
|
|
|
|
|
OnResolving();
|
2016-02-10 14:39:11 +01:00
|
|
|
|
var links = await SearchHelper.FindYoutubeUrlByKeywords(Query);
|
2016-02-23 01:21:22 +01:00
|
|
|
|
if (links == String.Empty)
|
|
|
|
|
throw new OperationCanceledException("Not a valid youtube query.");
|
2016-02-18 03:29:30 +01:00
|
|
|
|
var allVideos = await Task.Factory.StartNew(async () => await YouTube.Default.GetAllVideosAsync(links)).Unwrap();
|
2016-02-10 13:44:19 +01:00
|
|
|
|
var videos = allVideos.Where(v => v.AdaptiveKind == AdaptiveKind.Audio);
|
2016-02-04 18:24:06 +01:00
|
|
|
|
var video = videos
|
2016-02-11 10:33:57 +01:00
|
|
|
|
.Where(v => v.AudioBitrate < 192)
|
2016-02-10 13:44:19 +01:00
|
|
|
|
.OrderByDescending(v => v.AudioBitrate)
|
|
|
|
|
.FirstOrDefault();
|
2016-02-04 18:24:06 +01:00
|
|
|
|
|
|
|
|
|
if (video == null) // do something with this error
|
|
|
|
|
throw new Exception("Could not load any video elements based on the query.");
|
|
|
|
|
|
2016-02-14 23:48:07 +01:00
|
|
|
|
Title = video.Title.Substring(0, video.Title.Length - 10); // removing trailing "- You Tube"
|
2016-02-06 14:43:03 +01:00
|
|
|
|
Provider = "YouTube";
|
2016-02-04 18:24:06 +01:00
|
|
|
|
uri = video.Uri;
|
|
|
|
|
}
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) {
|
2016-01-29 13:28:51 +01:00
|
|
|
|
privateState = StreamState.Completed;
|
2016-01-30 13:51:47 +01:00
|
|
|
|
if (OnResolvingFailed != null)
|
|
|
|
|
OnResolvingFailed(ex.Message);
|
2016-01-29 13:28:51 +01:00
|
|
|
|
Console.WriteLine($"Failed resolving the link.{ex.Message}");
|
|
|
|
|
return;
|
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-02-04 18:24:06 +01:00
|
|
|
|
musicStreamer = new MusicStreamer(this, uri);
|
2016-01-29 13:28:51 +01:00
|
|
|
|
if (OnQueued != null)
|
|
|
|
|
OnQueued();
|
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
internal string PrintStats() => musicStreamer?.Stats();
|
|
|
|
|
|
2016-01-26 20:18:48 +01:00
|
|
|
|
public Action OnQueued = null;
|
|
|
|
|
public Action OnBuffering = null;
|
|
|
|
|
public Action OnStarted = null;
|
|
|
|
|
public Action OnCompleted = null;
|
2016-01-30 13:51:47 +01:00
|
|
|
|
public Action OnResolving = null;
|
|
|
|
|
public Action<string> OnResolvingFailed = null;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
|
|
|
|
internal void Cancel() {
|
2016-01-28 04:38:26 +01:00
|
|
|
|
musicStreamer?.Cancel();
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-29 13:28:51 +01:00
|
|
|
|
internal void Stop() {
|
|
|
|
|
musicStreamer?.Stop();
|
|
|
|
|
}
|
|
|
|
|
|
2016-01-30 05:24:32 +01:00
|
|
|
|
internal async Task Start() {
|
|
|
|
|
int attemptsLeft = 4;
|
|
|
|
|
//wait for up to 4 seconds to resolve a link
|
|
|
|
|
try {
|
2016-01-26 20:18:48 +01:00
|
|
|
|
while (State == StreamState.Resolving) {
|
|
|
|
|
await Task.Delay(1000);
|
|
|
|
|
if (--attemptsLeft == 0) {
|
2016-01-29 13:28:51 +01:00
|
|
|
|
throw new TimeoutException("Resolving timed out.");
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-30 05:24:32 +01:00
|
|
|
|
await musicStreamer.StartPlayback();
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
catch (TimeoutException) {
|
2016-01-30 05:24:32 +01:00
|
|
|
|
Console.WriteLine("Resolving timed out.");
|
|
|
|
|
privateState = StreamState.Completed;
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
catch (Exception ex) {
|
2016-01-30 05:24:32 +01:00
|
|
|
|
Console.WriteLine("Error in start playback." + ex.Message);
|
|
|
|
|
privateState = StreamState.Completed;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class MusicStreamer {
|
|
|
|
|
private DualStream buffer;
|
|
|
|
|
|
|
|
|
|
public StreamState State { get; internal set; }
|
2016-01-28 04:38:26 +01:00
|
|
|
|
public string Url { get; }
|
|
|
|
|
private bool IsCanceled { get; set; }
|
2016-01-29 13:28:51 +01:00
|
|
|
|
public bool IsPaused => parent.IsPaused;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
|
|
|
|
StreamRequest parent;
|
|
|
|
|
private readonly object _bufferLock = new object();
|
2016-01-30 05:24:32 +01:00
|
|
|
|
private bool prebufferingComplete = false;
|
2016-01-27 06:33:31 +01:00
|
|
|
|
|
2016-02-02 00:05:41 +01:00
|
|
|
|
public MusicStreamer(StreamRequest parent, string directUrl) {
|
2016-01-26 20:18:48 +01:00
|
|
|
|
this.parent = parent;
|
|
|
|
|
this.buffer = new DualStream();
|
|
|
|
|
this.Url = directUrl;
|
|
|
|
|
State = StreamState.Queued;
|
2016-01-27 06:33:31 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
public string Stats() =>
|
|
|
|
|
"--------------------------------\n" +
|
2016-01-30 12:08:30 +01:00
|
|
|
|
$"Music stats for {string.Join("", parent.Title.TrimTo(50))}\n" +
|
2016-01-28 04:38:26 +01:00
|
|
|
|
$"Server: {parent.Server.Name}\n" +
|
|
|
|
|
$"Length:{buffer.Length * 1.0f / 1.MB()}MB Status: {State}\n" +
|
|
|
|
|
"--------------------------------\n";
|
2016-02-14 23:48:07 +01:00
|
|
|
|
|
2016-01-26 20:18:48 +01:00
|
|
|
|
private async Task BufferSong() {
|
|
|
|
|
//start feeding the buffer
|
2016-02-08 10:30:59 +01:00
|
|
|
|
|
2016-02-24 02:43:27 +01:00
|
|
|
|
Process p = null;
|
|
|
|
|
try {
|
|
|
|
|
var psi = new ProcessStartInfo {
|
|
|
|
|
FileName = "ffmpeg",
|
|
|
|
|
Arguments = $"-i {Url} -f s16le -ar 48000 -ac 2 pipe:1 -loglevel quiet", //+ (NadekoBot.IsLinux ? "2> /dev/null" : "2>NUL"),
|
|
|
|
|
UseShellExecute = false,
|
|
|
|
|
RedirectStandardOutput = true,
|
|
|
|
|
};
|
|
|
|
|
using (p = Process.Start(psi)) {
|
|
|
|
|
int attempt = 0;
|
|
|
|
|
while (true) {
|
|
|
|
|
while (buffer.writePos - buffer.readPos > 5.MB() && State != StreamState.Completed) {
|
|
|
|
|
prebufferingComplete = true;
|
|
|
|
|
await Task.Delay(200);
|
2016-02-22 00:44:03 +01:00
|
|
|
|
}
|
2016-02-24 02:43:27 +01:00
|
|
|
|
|
|
|
|
|
if (State == StreamState.Completed) {
|
|
|
|
|
Console.WriteLine("Buffering canceled, stream is completed.");
|
2016-02-24 06:32:01 +01:00
|
|
|
|
p.CancelOutputRead();
|
2016-02-24 02:43:27 +01:00
|
|
|
|
p.Close();
|
2016-02-22 00:44:03 +01:00
|
|
|
|
return;
|
|
|
|
|
}
|
2016-02-24 02:43:27 +01:00
|
|
|
|
if (buffer.readPos > 5.MiB() && buffer.writePos > 5.MiB()) {
|
|
|
|
|
var skip = 5.MB();
|
|
|
|
|
lock (_bufferLock) {
|
|
|
|
|
byte[] data = new byte[buffer.Length - skip];
|
|
|
|
|
Buffer.BlockCopy(buffer.GetBuffer(), skip, data, 0, (int)(buffer.Length - skip));
|
|
|
|
|
var newReadPos = buffer.readPos - skip;
|
|
|
|
|
var newPos = buffer.Position - skip;
|
|
|
|
|
buffer = new DualStream();
|
|
|
|
|
buffer.Write(data, 0, data.Length);
|
|
|
|
|
buffer.readPos = newReadPos;
|
|
|
|
|
buffer.Position = newPos;
|
|
|
|
|
}
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
2016-02-24 02:43:27 +01:00
|
|
|
|
int blockSize = 1920 * NadekoBot.client.GetService<AudioService>()?.Config?.Channels ?? 3840;
|
|
|
|
|
var buf = new byte[blockSize];
|
|
|
|
|
int read = 0;
|
|
|
|
|
read = await p.StandardOutput.BaseStream.ReadAsync(buf, 0, blockSize);
|
|
|
|
|
//Console.WriteLine($"Read: {read}");
|
|
|
|
|
if (read == 0) {
|
|
|
|
|
if (attempt == 5) {
|
|
|
|
|
Console.WriteLine($"Didn't read anything from the stream for {attempt} attempts. {buffer.Length / 1.MB()}MB length");
|
2016-02-24 06:32:01 +01:00
|
|
|
|
p.CancelOutputRead();
|
2016-02-24 02:43:27 +01:00
|
|
|
|
p.Close();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
++attempt;
|
|
|
|
|
await Task.Delay(20);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
attempt = 0;
|
|
|
|
|
lock (_bufferLock) {
|
|
|
|
|
buffer.Write(buf, 0, read);
|
|
|
|
|
}
|
2016-02-22 00:44:03 +01:00
|
|
|
|
}
|
2016-02-19 02:20:28 +01:00
|
|
|
|
}
|
2016-02-08 10:30:59 +01:00
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
2016-02-24 02:43:27 +01:00
|
|
|
|
catch { }
|
|
|
|
|
finally {
|
|
|
|
|
if (p != null) {
|
2016-02-24 06:32:01 +01:00
|
|
|
|
p.CancelOutputRead();
|
2016-02-24 02:43:27 +01:00
|
|
|
|
p.Close();
|
|
|
|
|
p.Dispose();
|
|
|
|
|
p = null;
|
|
|
|
|
}
|
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
internal async Task StartPlayback() {
|
|
|
|
|
Console.WriteLine("Starting playback.");
|
2016-01-29 13:28:51 +01:00
|
|
|
|
if (State == StreamState.Playing) return;
|
2016-01-28 04:38:26 +01:00
|
|
|
|
State = StreamState.Playing;
|
|
|
|
|
if (parent.OnBuffering != null)
|
|
|
|
|
parent.OnBuffering();
|
2016-02-04 14:17:15 +01:00
|
|
|
|
|
2016-02-22 00:44:03 +01:00
|
|
|
|
Task.Factory.StartNew(async () => {
|
2016-02-04 14:17:15 +01:00
|
|
|
|
await BufferSong();
|
2016-02-06 14:43:03 +01:00
|
|
|
|
}).ConfigureAwait(false);
|
2016-01-30 05:24:32 +01:00
|
|
|
|
|
|
|
|
|
// prebuffering wait stuff start
|
|
|
|
|
int bufferAttempts = 0;
|
|
|
|
|
int waitPerAttempt = 500;
|
2016-02-16 04:20:56 +01:00
|
|
|
|
int toAttemptTimes = parent.RadioLink ? 4 : 8;
|
|
|
|
|
while (!prebufferingComplete && bufferAttempts++ < toAttemptTimes) {
|
2016-01-30 05:24:32 +01:00
|
|
|
|
await Task.Delay(waitPerAttempt);
|
|
|
|
|
}
|
|
|
|
|
if (prebufferingComplete) {
|
2016-02-14 23:48:07 +01:00
|
|
|
|
Console.WriteLine($"Prebuffering finished in {bufferAttempts * 500}");
|
2016-01-28 04:38:26 +01:00
|
|
|
|
}
|
2016-01-30 05:24:32 +01:00
|
|
|
|
// prebuffering wait stuff end
|
2016-02-14 23:48:07 +01:00
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
if (buffer.Length > 0) {
|
2016-01-26 20:18:48 +01:00
|
|
|
|
Console.WriteLine("Prebuffering complete.");
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
else {
|
2016-02-09 23:12:54 +01:00
|
|
|
|
Console.WriteLine("Nothing was buffered, try another song and check your GoogleApikey.");
|
2016-01-28 04:38:26 +01:00
|
|
|
|
}
|
2016-02-14 23:48:07 +01:00
|
|
|
|
|
2016-02-21 05:56:29 +01:00
|
|
|
|
int blockSize = 1920 * NadekoBot.client.GetService<AudioService>()?.Config?.Channels ?? 3840;
|
2016-01-28 04:38:26 +01:00
|
|
|
|
byte[] voiceBuffer = new byte[blockSize];
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-01-30 13:51:47 +01:00
|
|
|
|
if (parent.OnStarted != null)
|
|
|
|
|
parent.OnStarted();
|
|
|
|
|
|
2016-01-29 08:23:15 +01:00
|
|
|
|
int attempt = 0;
|
2016-01-28 04:38:26 +01:00
|
|
|
|
while (!IsCanceled) {
|
|
|
|
|
int readCount = 0;
|
2016-02-04 14:17:15 +01:00
|
|
|
|
//adjust volume
|
2016-02-14 23:48:07 +01:00
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
lock (_bufferLock) {
|
2016-02-19 02:20:28 +01:00
|
|
|
|
readCount = buffer.Read(voiceBuffer, 0, blockSize);
|
2016-01-28 04:38:26 +01:00
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
if (readCount == 0) {
|
2016-02-04 06:46:30 +01:00
|
|
|
|
if (attempt == 4) {
|
2016-02-19 02:20:28 +01:00
|
|
|
|
Console.WriteLine($"Failed to read {attempt} times. Breaking out.");
|
2016-01-29 08:23:15 +01:00
|
|
|
|
break;
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
else {
|
2016-01-29 08:23:15 +01:00
|
|
|
|
++attempt;
|
2016-02-04 06:46:30 +01:00
|
|
|
|
await Task.Delay(15);
|
2016-01-29 08:23:15 +01:00
|
|
|
|
}
|
2016-02-14 23:48:07 +01:00
|
|
|
|
}
|
|
|
|
|
else
|
2016-01-29 08:23:15 +01:00
|
|
|
|
attempt = 0;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
if (State == StreamState.Completed) {
|
|
|
|
|
Console.WriteLine("Canceled");
|
|
|
|
|
break;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
2016-02-04 14:17:15 +01:00
|
|
|
|
voiceBuffer = adjustVolume(voiceBuffer, parent.Volume);
|
2016-02-19 02:20:28 +01:00
|
|
|
|
parent.MusicControls.VoiceClient.Send(voiceBuffer, 0, readCount);
|
2016-01-29 13:28:51 +01:00
|
|
|
|
|
|
|
|
|
while (IsPaused) {
|
2016-02-20 17:38:02 +01:00
|
|
|
|
await Task.Delay(100);
|
2016-01-29 13:28:51 +01:00
|
|
|
|
}
|
2016-01-28 04:38:26 +01:00
|
|
|
|
}
|
2016-01-31 19:51:27 +01:00
|
|
|
|
parent.MusicControls.VoiceClient.Wait();
|
2016-01-29 13:28:51 +01:00
|
|
|
|
Stop();
|
2016-01-28 04:38:26 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
internal void Cancel() {
|
|
|
|
|
IsCanceled = true;
|
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
2016-01-29 13:28:51 +01:00
|
|
|
|
internal void Stop() {
|
2016-02-04 14:17:15 +01:00
|
|
|
|
if (State == StreamState.Completed) return;
|
|
|
|
|
var oldState = State;
|
|
|
|
|
State = StreamState.Completed;
|
|
|
|
|
if (oldState == StreamState.Playing)
|
|
|
|
|
if (parent.OnCompleted != null)
|
|
|
|
|
parent.OnCompleted();
|
|
|
|
|
}
|
|
|
|
|
//stackoverflow ftw
|
|
|
|
|
private byte[] adjustVolume(byte[] audioSamples, float volume) {
|
|
|
|
|
if (volume == 1.0f)
|
|
|
|
|
return audioSamples;
|
|
|
|
|
byte[] array = new byte[audioSamples.Length];
|
|
|
|
|
for (int i = 0; i < array.Length; i += 2) {
|
|
|
|
|
|
|
|
|
|
// convert byte pair to int
|
|
|
|
|
short buf1 = audioSamples[i + 1];
|
|
|
|
|
short buf2 = audioSamples[i];
|
|
|
|
|
|
|
|
|
|
buf1 = (short)((buf1 & 0xff) << 8);
|
|
|
|
|
buf2 = (short)(buf2 & 0xff);
|
|
|
|
|
|
|
|
|
|
short res = (short)(buf1 | buf2);
|
|
|
|
|
res = (short)(res * volume);
|
|
|
|
|
|
|
|
|
|
// convert back
|
|
|
|
|
array[i] = (byte)res;
|
|
|
|
|
array[i + 1] = (byte)(res >> 8);
|
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
}
|
2016-02-04 14:17:15 +01:00
|
|
|
|
return array;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public class DualStream : MemoryStream {
|
|
|
|
|
public long readPos;
|
|
|
|
|
public long writePos;
|
|
|
|
|
|
2016-01-28 04:38:26 +01:00
|
|
|
|
public DualStream() : base() {
|
|
|
|
|
readPos = writePos = 0;
|
|
|
|
|
}
|
2016-01-26 20:18:48 +01:00
|
|
|
|
|
|
|
|
|
public override int Read(byte[] buffer, int offset, int count) {
|
2016-02-22 00:11:07 +01:00
|
|
|
|
Position = readPos;
|
|
|
|
|
int read = base.Read(buffer, offset, count);
|
|
|
|
|
readPos = Position;
|
|
|
|
|
return read;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
public override void Write(byte[] buffer, int offset, int count) {
|
2016-02-22 00:11:07 +01:00
|
|
|
|
Position = writePos;
|
|
|
|
|
base.Write(buffer, offset, count);
|
|
|
|
|
writePos = Position;
|
2016-01-26 20:18:48 +01:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|