Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion karma.conf.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ module.exports = function (config) {
clearContext: false,
jasmine: {
random: false,
stopSpecOnExpectationFailure: false
stopSpecOnExpectationFailure: false.valueOf,
timeoutInterval: 30000
}
},

Expand Down
5 changes: 2 additions & 3 deletions src.csharp/AlphaTab.Windows/NAudioSynthOutput.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ public class NAudioSynthOutput : WaveProvider32, ISynthOutput, IDisposable
{
private const int BufferSize = 4096;
private const int PreferredSampleRate = 44100;
private const int TotalBufferTimeInMilliseconds = 5000;

private DirectSoundOut _context;
private CircularSampleBuffer _circularBuffer;
Expand All @@ -41,10 +40,10 @@ public void Activate()


/// <inheritdoc />
public void Open()
public void Open(double bufferTimeInMilliseconds)
{
_bufferCount = (int)(
(TotalBufferTimeInMilliseconds * PreferredSampleRate) /
(bufferTimeInMilliseconds * PreferredSampleRate) /
1000 /
BufferSize
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ internal abstract class AlphaSynthWorkerApiBase : IAlphaSynth
{
private readonly ISynthOutput _output;
private LogLevel _logLevel;
private double _bufferTimeInMilliseconds;

protected AlphaSynth Player;

protected AlphaSynthWorkerApiBase(ISynthOutput output, LogLevel logLevel)
protected AlphaSynthWorkerApiBase(ISynthOutput output, LogLevel logLevel, double bufferTimeInMilliseconds)
{
_output = output;
_logLevel = logLevel;
_bufferTimeInMilliseconds = bufferTimeInMilliseconds;
Player = null!;
}

Expand All @@ -26,7 +28,7 @@ protected AlphaSynthWorkerApiBase(ISynthOutput output, LogLevel logLevel)

protected void Initialize()
{
Player = new AlphaSynth(_output);
Player = new AlphaSynth(_output, _bufferTimeInMilliseconds);
Player.PositionChanged.On(OnPositionChanged);
Player.StateChanged.On(OnStateChanged);
Player.Finished.On(OnFinished);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ internal class ManagedThreadAlphaSynthWorkerApi : AlphaSynthWorkerApiBase
private CancellationTokenSource _workerCancellationToken;
private readonly ManualResetEventSlim? _threadStartedEvent;

public ManagedThreadAlphaSynthWorkerApi(ISynthOutput output, LogLevel logLevel, Action<Action> uiInvoke)
: base(output, logLevel)
public ManagedThreadAlphaSynthWorkerApi(ISynthOutput output, LogLevel logLevel, Action<Action> uiInvoke, double bufferTimeInMilliseconds)
: base(output, logLevel, bufferTimeInMilliseconds)
{
_uiInvoke = uiInvoke;

Expand Down
2 changes: 1 addition & 1 deletion src.csharp/AlphaTab/Platform/CSharp/ManagedUiFacade.cs
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public IScoreRenderer CreateWorkerRenderer()
public IAlphaSynth CreateWorkerPlayer()
{
var player = new ManagedThreadAlphaSynthWorkerApi(CreateSynthOutput(),
Api.Settings.Core.LogLevel, BeginInvoke);
Api.Settings.Core.LogLevel, BeginInvoke, Api.Settings.Player.BufferTimeInMilliseconds);
player.Ready.On(() =>
{
using (var sf = OpenDefaultSoundFont())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ internal class AndroidSynthOutput(
companion object {
private const val BufferSize = 4096
private const val PreferredSampleRate = 44100
private const val TotalBufferTimeInMilliseconds = 5000
}

private var _bufferCount = 0
Expand All @@ -31,10 +30,10 @@ internal class AndroidSynthOutput(
override fun activate() {
}

override fun open() {
_bufferCount = (TotalBufferTimeInMilliseconds * PreferredSampleRate /
override fun open(bufferTimeInMilliseconds: Double) {
_bufferCount = (bufferTimeInMilliseconds * PreferredSampleRate /
1000 /
BufferSize)
BufferSize).toInt()
_circularBuffer = CircularSampleBuffer((BufferSize * _bufferCount).toDouble())

_audioContext = AndroidAudioWorker(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@ internal class AndroidThreadAlphaSynthWorkerPlayer : IAlphaSynth, Runnable {
private var _player: AlphaSynth? = null
private val _output: ISynthOutput
private var _logLevel: LogLevel
private var _bufferTimeInMilliseconds: Double

constructor(
logLevel: LogLevel,
output: ISynthOutput,
uiInvoke: (action: (() -> Unit)) -> Unit
uiInvoke: (action: (() -> Unit)) -> Unit,
bufferTimeInMilliseconds: Double
) {
_logLevel = logLevel
_bufferTimeInMilliseconds = bufferTimeInMilliseconds
_output = output
_uiInvoke = uiInvoke
_threadStartedEvent = Semaphore(1)
Expand Down Expand Up @@ -78,7 +81,7 @@ internal class AndroidThreadAlphaSynthWorkerPlayer : IAlphaSynth, Runnable {
}

private fun initialize() {
val player = AlphaSynth(_output)
val player = AlphaSynth(_output, _bufferTimeInMilliseconds)
_player = player
player.positionChanged.on {
_uiInvoke { onPositionChanged(it) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,8 @@ internal class AndroidUiFacade : IUiFacade<AlphaTabView> {
AndroidSynthOutput {
player!!.addToWorker(it)
},
this::beginInvoke
this::beginInvoke,
api.settings.player.bufferTimeInMilliseconds
)
player.ready.on {
val soundFont = openDefaultSoundFont()
Expand Down
7 changes: 7 additions & 0 deletions src/PlayerSettings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,11 @@ export class PlayerSettings {
* Gets or sets whether the triplet feel should be applied/played during audio playback.
*/
public playTripletFeel: boolean = true;

/**
* Gets or sets how many milliseconds of audio samples should be buffered in total.
* Larger buffers cause a delay from when audio settings like volumes will be applied.
* Smaller buffers can cause audio crackling due to constant buffering that is happening.
*/
public bufferTimeInMilliseconds:number = 500;
}
4 changes: 4 additions & 0 deletions src/generated/PlayerSettingsSerializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export class PlayerSettingsSerializer {
o.set("vibrato", VibratoPlaybackSettingsSerializer.toJson(obj.vibrato));
o.set("slide", SlidePlaybackSettingsSerializer.toJson(obj.slide));
o.set("playtripletfeel", obj.playTripletFeel);
o.set("buffertimeinmilliseconds", obj.bufferTimeInMilliseconds);
return o;
}
public static setProperty(obj: PlayerSettings, property: string, v: unknown): boolean {
Expand Down Expand Up @@ -88,6 +89,9 @@ export class PlayerSettingsSerializer {
case "playtripletfeel":
obj.playTripletFeel = v! as boolean;
return true;
case "buffertimeinmilliseconds":
obj.bufferTimeInMilliseconds = v! as number;
return true;
}
if (["vibrato"].indexOf(property) >= 0) {
VibratoPlaybackSettingsSerializer.fromJson(obj.vibrato, v as Map<string, unknown>);
Expand Down
30 changes: 18 additions & 12 deletions src/platform/javascript/AlphaSynthAudioWorkletOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,29 +41,27 @@ declare var sampleRate: number;
export class AlphaSynthWebWorklet {
private static _isRegistered = false;
public static init() {
if(AlphaSynthWebWorklet._isRegistered) {
if (AlphaSynthWebWorklet._isRegistered) {
return;
}
AlphaSynthWebWorklet._isRegistered = true;
AlphaSynthWebWorklet._isRegistered = true;
registerProcessor(
'alphatab',
class AlphaSynthWebWorkletProcessor extends AudioWorkletProcessor {
public static readonly BufferSize: number = 4096;
private static readonly TotalBufferTimeInMilliseconds: number = 5000;

private _outputBuffer: Float32Array = new Float32Array(0);
private _circularBuffer!: CircularSampleBuffer;
private _bufferCount: number = 0;
private _requestedBufferCount: number = 0;

constructor(...args: any[]) {
super(...args);
constructor(options: AudioWorkletNodeOptions) {
super(options);

Logger.debug('WebAudio', 'creating processor');

this._bufferCount = Math.floor(
(AlphaSynthWebWorkletProcessor.TotalBufferTimeInMilliseconds *
sampleRate) /
(options.processorOptions.bufferTimeInMilliseconds * sampleRate) /
1000 /
AlphaSynthWebWorkletProcessor.BufferSize
);
Expand Down Expand Up @@ -111,7 +109,11 @@ export class AlphaSynthWebWorklet {
buffer = new Float32Array(samples);
this._outputBuffer = buffer;
}
const samplesFromBuffer = this._circularBuffer.read(buffer, 0, Math.min(buffer.length, this._circularBuffer.count));
const samplesFromBuffer = this._circularBuffer.read(
buffer,
0,
Math.min(buffer.length, this._circularBuffer.count)
);
let s: number = 0;
for (let i: number = 0; i < left.length; i++) {
left[i] = buffer[s++];
Expand Down Expand Up @@ -158,9 +160,11 @@ export class AlphaSynthWebWorklet {
*/
export class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
private _worklet: AudioWorkletNode | null = null;
private _bufferTimeInMilliseconds: number = 0;

public override open() {
super.open();
public override open(bufferTimeInMilliseconds: number) {
super.open(bufferTimeInMilliseconds);
this._bufferTimeInMilliseconds = bufferTimeInMilliseconds;
this.onReady();
}

Expand All @@ -172,10 +176,12 @@ export class AlphaSynthAudioWorkletOutput extends AlphaSynthWebAudioOutputBase {
() => {
this._worklet = new AudioWorkletNode(ctx!, 'alphatab', {
numberOfOutputs: 1,
outputChannelCount: [2]
outputChannelCount: [2],
processorOptions: {
bufferTimeInMilliseconds: this._bufferTimeInMilliseconds
}
});
this._worklet.port.onmessage = this.handleMessage.bind(this);

this._source!.connect(this._worklet);
this._source!.start(0);
this._worklet.connect(ctx!.destination);
Expand Down
14 changes: 8 additions & 6 deletions src/platform/javascript/AlphaSynthScriptProcessorOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,10 @@ export class AlphaSynthScriptProcessorOutput extends AlphaSynthWebAudioOutputBas
private _bufferCount: number = 0;
private _requestedBufferCount: number = 0;

public override open() {
super.open();
public override open(bufferTimeInMilliseconds: number) {
super.open(bufferTimeInMilliseconds);
this._bufferCount = Math.floor(
(AlphaSynthWebAudioOutputBase.TotalBufferTimeInMilliseconds * this.sampleRate) /
1000 /
AlphaSynthWebAudioOutputBase.BufferSize
(bufferTimeInMilliseconds * this.sampleRate) / 1000 / AlphaSynthWebAudioOutputBase.BufferSize
);
this._circularBuffer = new CircularSampleBuffer(AlphaSynthWebAudioOutputBase.BufferSize * this._bufferCount);
this.onReady();
Expand Down Expand Up @@ -87,7 +85,11 @@ export class AlphaSynthScriptProcessorOutput extends AlphaSynthWebAudioOutputBas
buffer = new Float32Array(samples);
this._outputBuffer = buffer;
}
const samplesFromBuffer = this._circularBuffer.read(buffer, 0, Math.min(buffer.length, this._circularBuffer.count));
const samplesFromBuffer = this._circularBuffer.read(
buffer,
0,
Math.min(buffer.length, this._circularBuffer.count)
);
let s: number = 0;
for (let i: number = 0; i < left.length; i++) {
left[i] = buffer[s++];
Expand Down
7 changes: 3 additions & 4 deletions src/platform/javascript/AlphaSynthWebAudioOutputBase.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ declare var webkitAudioContext: any;
export abstract class AlphaSynthWebAudioOutputBase implements ISynthOutput {
protected static readonly BufferSize: number = 4096;
protected static readonly PreferredSampleRate: number = 44100;
protected static readonly TotalBufferTimeInMilliseconds: number = 5000;

protected _context: AudioContext | null = null;
protected _buffer: AudioBuffer | null = null;
Expand Down Expand Up @@ -75,15 +74,15 @@ export abstract class AlphaSynthWebAudioOutputBase implements ISynthOutput {
throw new AlphaTabError(AlphaTabErrorType.General, 'AudioContext not found');
}

public open(): void {
public open(bufferTimeInMilliseconds: number): void {
this.patchIosSampleRate();
this._context = this.createAudioContext();
let ctx: any = this._context;
if (ctx.state === 'suspended') {
this.registerResumeHandler();
}
}

private registerResumeHandler() {
this._resumeHandler = (() => {
this.activate(() => {
Expand All @@ -93,7 +92,7 @@ export abstract class AlphaSynthWebAudioOutputBase implements ISynthOutput {
document.body.addEventListener('touchend', this._resumeHandler, false);
document.body.addEventListener('click', this._resumeHandler, false);
}

private unregisterResumeHandler() {
const resumeHandler = this._resumeHandler;
if (resumeHandler) {
Expand Down
6 changes: 3 additions & 3 deletions src/platform/javascript/AlphaSynthWebWorker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,11 @@ export class AlphaSynthWebWorker {
private _player: AlphaSynth;
private _main: IWorkerScope;

public constructor(main: IWorkerScope) {
public constructor(main: IWorkerScope, bufferTimeInMilliseconds:number) {
this._main = main;
this._main.addEventListener('message', this.handleMessage.bind(this));

this._player = new AlphaSynth(new AlphaSynthWorkerSynthOutput());
this._player = new AlphaSynth(new AlphaSynthWorkerSynthOutput(), bufferTimeInMilliseconds);
this._player.positionChanged.on(this.onPositionChanged.bind(this));
this._player.stateChanged.on(this.onPlayerStateChanged.bind(this));
this._player.finished.on(this.onFinished.bind(this));
Expand All @@ -48,7 +48,7 @@ export class AlphaSynthWebWorker {
case 'alphaSynth.initialize':
AlphaSynthWorkerSynthOutput.preferredSampleRate = data.sampleRate;
Logger.logLevel = data.logLevel;
Environment.globalThis.alphaSynthWebWorker = new AlphaSynthWebWorker(main);
Environment.globalThis.alphaSynthWebWorker = new AlphaSynthWebWorker(main, data.bufferTimeInMilliseconds);
break;
}
});
Expand Down
Loading