diff --git a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
index bfa349f37..146737935 100644
--- a/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
+++ b/src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
@@ -14,7 +14,7 @@ public class NAudioSynthOutput : WaveProvider32, ISynthOutput, IDisposable
{
private const int BufferSize = 4096;
private const int PreferredSampleRate = 44100;
-
+
private DirectSoundOut _context;
private CircularSampleBuffer _circularBuffer;
private int _bufferCount = 0;
@@ -27,7 +27,7 @@ public class NAudioSynthOutput : WaveProvider32, ISynthOutput, IDisposable
/// Initializes a new instance of the class.
///
public NAudioSynthOutput()
- : base(PreferredSampleRate, 2)
+ : base(PreferredSampleRate, (int)SynthConstants.AudioChannels)
{
_context = null!;
_circularBuffer = null!;
@@ -42,16 +42,21 @@ public void Activate()
///
public void Open(double bufferTimeInMilliseconds)
{
+ var latency = 40;
+ _context = new DirectSoundOut(latency);
+ _context.Init(this);
+
+ // NAudio introduces another level of buffering and latency
+ // we've seen that this can cause our buffers to deplete
+ // as a mitigation we buffer a lot more
_bufferCount = (int)(
(bufferTimeInMilliseconds * PreferredSampleRate) /
1000 /
BufferSize
- );
+ ) * 4;
_circularBuffer = new CircularSampleBuffer(BufferSize * _bufferCount);
- _context = new DirectSoundOut(100);
- _context.Init(this);
- ((EventEmitter) Ready).Trigger();
+ ((EventEmitter)Ready).Trigger();
}
///
@@ -112,12 +117,12 @@ private void RequestBuffers()
// before we already get samples via addSamples, therefore we need to
// remember how many buffers have been requested, and consider them as available.
var bufferedSamples = _circularBuffer.Count + _requestedBufferCount * BufferSize;
-
+
if (bufferedSamples < halfSamples)
{
for (var i = 0; i < halfBufferCount; i++)
{
- ((EventEmitter) SampleRequest).Trigger();
+ ((EventEmitter)SampleRequest).Trigger();
_requestedBufferCount++;
}
}
@@ -127,17 +132,19 @@ private void RequestBuffers()
public override int Read(float[] buffer, int offset, int count)
{
var read = new Float32Array(count);
-
- var samplesFromBuffer = _circularBuffer.Read(read, 0, System.Math.Min(read.Length, _circularBuffer.Count));
+
+ var samplesFromBuffer = (int)_circularBuffer.Read(read, 0,
+ System.Math.Min(read.Length, _circularBuffer.Count));
Buffer.BlockCopy(read.Data, 0, buffer, offset * sizeof(float),
- count * sizeof(float));
+ samplesFromBuffer * sizeof(float));
- var samples = count / 2;
- ((EventEmitterOfT) SamplesPlayed).Trigger(samples / SynthConstants.AudioChannels);
+ ((EventEmitterOfT)SamplesPlayed).Trigger(samplesFromBuffer /
+ SynthConstants.AudioChannels);
RequestBuffers();
+
return count;
}
diff --git a/src.csharp/AlphaTab.Windows/WinForms/SkiaUtil.cs b/src.csharp/AlphaTab.Windows/WinForms/AlphaSkiaUtil.cs
similarity index 72%
rename from src.csharp/AlphaTab.Windows/WinForms/SkiaUtil.cs
rename to src.csharp/AlphaTab.Windows/WinForms/AlphaSkiaUtil.cs
index 53937947b..de83db1b2 100644
--- a/src.csharp/AlphaTab.Windows/WinForms/SkiaUtil.cs
+++ b/src.csharp/AlphaTab.Windows/WinForms/AlphaSkiaUtil.cs
@@ -1,13 +1,14 @@
using System.Drawing;
using System.Drawing.Imaging;
-using AlphaSkia;
+using AlphaTab.Platform.Skia.AlphaSkiaBridge;
namespace AlphaTab.WinForms
{
- internal static class SkiaUtil
+ internal static class AlphaSkiaUtil
{
- public static Bitmap ToBitmap(AlphaSkiaImage image)
+ public static Bitmap ToBitmap(AlphaSkiaImage imageBridge)
{
+ var image = imageBridge.Image;
var bitmap = new Bitmap(image.Width, image.Height, PixelFormat.Format32bppPArgb);
var bitmapData =
bitmap.LockBits(new Rectangle(Point.Empty, bitmap.Size), ImageLockMode.WriteOnly,
diff --git a/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs b/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs
index 7d215d153..c695c0643 100644
--- a/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs
+++ b/src.csharp/AlphaTab.Windows/WinForms/WinFormsUiFacade.cs
@@ -3,10 +3,10 @@
using System.Drawing;
using System.IO;
using System.Windows.Forms;
-using AlphaSkia;
using AlphaTab.Synth;
using AlphaTab.Platform;
using AlphaTab.Platform.CSharp;
+using AlphaTab.Platform.Skia.AlphaSkiaBridge;
using AlphaTab.Rendering;
using AlphaTab.Rendering.Utils;
@@ -146,7 +146,7 @@ public override void BeginUpdateRenderResults(RenderFinishedEventArgs? r)
case AlphaSkiaImage skiaImage:
using (skiaImage)
{
- source = SkiaUtil.ToBitmap(skiaImage);
+ source = AlphaSkiaUtil.ToBitmap(skiaImage);
}
break;
diff --git a/src.csharp/AlphaTab.Windows/Wpf/SkImageSource.cs b/src.csharp/AlphaTab.Windows/Wpf/AlphaSkiaImageSource.cs
similarity index 72%
rename from src.csharp/AlphaTab.Windows/Wpf/SkImageSource.cs
rename to src.csharp/AlphaTab.Windows/Wpf/AlphaSkiaImageSource.cs
index 9e2fec16d..76bf6fc79 100644
--- a/src.csharp/AlphaTab.Windows/Wpf/SkImageSource.cs
+++ b/src.csharp/AlphaTab.Windows/Wpf/AlphaSkiaImageSource.cs
@@ -1,14 +1,15 @@
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;
-using AlphaSkia;
+using AlphaTab.Platform.Skia.AlphaSkiaBridge;
namespace AlphaTab.Wpf
{
- internal static class SkImageSource
+ internal static class AlphaSkiaImageSource
{
- public static BitmapSource Create(AlphaSkiaImage image)
+ public static BitmapSource Create(AlphaSkiaImage imageBridge)
{
+ var image = imageBridge.Image;
var bitmap = new WriteableBitmap(image.Width, image.Height, 96, 96, PixelFormats.Pbgra32, null);
bitmap.Lock();
// copy
diff --git a/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs b/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs
index cf802c680..5c821f7c0 100644
--- a/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs
+++ b/src.csharp/AlphaTab.Windows/Wpf/WpfUiFacade.cs
@@ -7,10 +7,10 @@
using System.Windows.Data;
using System.Windows.Media;
using System.Windows.Shapes;
-using AlphaSkia;
using AlphaTab.Synth;
using AlphaTab.Platform;
using AlphaTab.Platform.CSharp;
+using AlphaTab.Platform.Skia.AlphaSkiaBridge;
using AlphaTab.Rendering;
using AlphaTab.Rendering.Utils;
using Point = System.Windows.Point;
@@ -158,7 +158,7 @@ public override void BeginUpdateRenderResults(RenderFinishedEventArgs? r)
{
using (skiaImage)
{
- source = SkImageSource.Create(skiaImage);
+ source = AlphaSkiaImageSource.Create(skiaImage);
}
}
else if (body is System.Drawing.Bitmap image)
diff --git a/src.csharp/AlphaTab/Platform/Skia/AlphaSkiaBridge/AlphaSkiaBridge.cs b/src.csharp/AlphaTab/Platform/Skia/AlphaSkiaBridge/AlphaSkiaBridge.cs
index 7b9f9e5e9..de86a1d85 100644
--- a/src.csharp/AlphaTab/Platform/Skia/AlphaSkiaBridge/AlphaSkiaBridge.cs
+++ b/src.csharp/AlphaTab/Platform/Skia/AlphaSkiaBridge/AlphaSkiaBridge.cs
@@ -18,11 +18,17 @@ internal enum AlphaSkiaTextBaseline
Bottom = AlphaSkia.AlphaSkiaTextBaseline.Bottom
}
-internal class AlphaSkiaImage : IDisposable
+///
+/// Bridge between alphaTab and
+///
+public class AlphaSkiaImage : IDisposable
{
- internal AlphaSkia.AlphaSkiaImage Image { get; }
- public double Width => Image.Width;
- public double Height => Image.Height;
+ ///
+ /// Gets the target .
+ ///
+ public AlphaSkia.AlphaSkiaImage Image { get; }
+ internal double Width => Image.Width;
+ internal double Height => Image.Height;
internal AlphaSkiaImage(AlphaSkia.AlphaSkiaImage image)
{
@@ -34,7 +40,7 @@ public void Dispose()
Image.Dispose();
}
- public ArrayBuffer? ReadPixels()
+ internal ArrayBuffer? ReadPixels()
{
var data = Image.ReadPixels();
if (data == null)
@@ -45,7 +51,7 @@ public void Dispose()
return new ArrayBuffer(new ArraySegment(data, 0, data.Length));
}
- public ArrayBuffer? ToPng()
+ internal ArrayBuffer? ToPng()
{
var data = Image.ToPng();
if (data == null)
@@ -56,13 +62,13 @@ public void Dispose()
return new ArrayBuffer(new ArraySegment(data, 0, data.Length));
}
- public static AlphaSkiaImage? Decode(ArrayBuffer buffer)
+ internal static AlphaSkiaImage? Decode(ArrayBuffer buffer)
{
var underlying = AlphaSkia.AlphaSkiaImage.Decode(buffer.Raw.Array!);
return underlying == null ? null : new AlphaSkiaImage(underlying);
}
- public static AlphaSkiaImage? FromPixels(double width, double height, ArrayBuffer pixels)
+ internal static AlphaSkiaImage? FromPixels(double width, double height, ArrayBuffer pixels)
{
var underlying =
AlphaSkia.AlphaSkiaImage.FromPixels((int)width, (int)height, pixels.Raw.Array!);
@@ -70,6 +76,9 @@ public void Dispose()
}
}
+///
+/// Bridge between alphaTab and
+///
internal class AlphaSkiaCanvas : IDisposable
{
private readonly AlphaSkia.AlphaSkiaCanvas _canvas = new();
@@ -177,10 +186,10 @@ public void Stroke()
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void FillText(string text, AlphaSkiaTypeface typeface, double fontSize, double x,
double y,
- AlphaSkiaTextAlign textAlign, AlphaSkiaTextBaseline baseline)
+ AlphaSkiaTextAlign textAlign, AlphaSkiaTextBaseline baselineBridge)
{
_canvas.FillText(text, typeface.Typeface, (float)fontSize, (float)x, (float)y,
- (AlphaSkia.AlphaSkiaTextAlign)textAlign, (AlphaSkia.AlphaSkiaTextBaseline)baseline);
+ (AlphaSkia.AlphaSkiaTextAlign)textAlign, (AlphaSkia.AlphaSkiaTextBaseline)baselineBridge);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]