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)]