Skip to content

Commit cbf80af

Browse files
fix: OPL3 port read delay and remove legacy ASM tests
Simulate 1.5us hardware latency in Opl3Fm port reads
1 parent 4ef0f34 commit cbf80af

11 files changed

Lines changed: 76 additions & 571 deletions

File tree

src/Spice86.Core/Emulator/Devices/Sound/Opl3Fm.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,12 @@ public override byte ReadByte(ushort port) {
178178
}
179179

180180
private byte PortRead(ushort port) {
181+
// Some tests revealed it taking 1.5us to read an AdLib port.
182+
// Expected by a lot of OPL music drivers in their startup routine, such as the one from 'Day of the Tentacle'
183+
// Without this the driver decides the OPL can't be used and no music is played.
184+
// Unlike the SB DSP reset delay which produces another (expected) response in SB port read path in the mean time,
185+
// this wait is blocking.
186+
_clock.Delay(TimeSpan.FromMicroseconds(1.5));
181187
switch (_mode) {
182188
case OplMode.Opl2:
183189
// We allocated 4 ports, so just return -1 for the higher ones.

src/Spice86.Core/Emulator/VM/Clock/ClockBase.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,7 @@ protected virtual void OnResumeCore() { }
6565
/// Called by <see cref="Dispose"/> before finalization is suppressed. Override to release additional resources.
6666
/// </summary>
6767
protected virtual void OnDisposeCore() { }
68+
69+
/// <inheritdoc />
70+
public abstract void Delay(TimeSpan timeSpan);
6871
}

src/Spice86.Core/Emulator/VM/Clock/CyclesClock.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
/// </summary>
88
public class CyclesClock : ClockBase {
99
private readonly State _cpuState;
10+
private TimeSpan _delay;
1011

1112
public CyclesClock(State cpuState, long cyclesPerSecond, int? jitterSeed, DateTimeOffset startTime)
1213
: base(ClockJitter.Create(jitterSeed), startTime) {
@@ -19,5 +20,10 @@ public CyclesClock(State cpuState, long cyclesPerSecond, int? jitterSeed, DateTi
1920

2021
/// <inheritdoc/>
2122
public override double ElapsedTimeMs =>
22-
(double)_cpuState.Cycles * 1000 / CyclesPerSecond + _jitter.Advance();
23+
(double)_cpuState.Cycles * 1000 / CyclesPerSecond + _jitter.Advance() + _delay.Microseconds;
24+
25+
/// <inheritdoc/>
26+
public override void Delay(TimeSpan timeSpan) {
27+
_delay += timeSpan;
28+
}
2329
}

src/Spice86.Core/Emulator/VM/Clock/EmulatedClock.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
public class EmulatedClock : ClockBase {
99
private int _ticks;
1010
private readonly Stopwatch _stopwatch = new();
11+
private TimeSpan _delay;
1112

1213
public EmulatedClock(int? jitterSeed, DateTimeOffset startTime)
1314
: base(ClockJitter.Create(jitterSeed), startTime) {
@@ -21,7 +22,7 @@ public override double ElapsedTimeMs {
2122
if (_ticks++ % 100 != 0) {
2223
return field;
2324
}
24-
field = _stopwatch.Elapsed.TotalMilliseconds + _jitter.Advance();
25+
field = _stopwatch.Elapsed.TotalMilliseconds + _jitter.Advance() + _delay.Microseconds;
2526
return field;
2627
}
2728
}
@@ -34,4 +35,9 @@ public override double ElapsedTimeMs {
3435

3536
/// <inheritdoc/>
3637
protected override void OnDisposeCore() => _stopwatch.Stop();
38+
39+
/// <inheritdoc/>
40+
public override void Delay(TimeSpan timeSpan) {
41+
_delay += timeSpan;
42+
}
3743
}

src/Spice86.Core/Emulator/VM/Clock/IEmulatedClock.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,14 @@ public interface IEmulatedClock : IDisposable {
2727
/// </summary>
2828
bool IsPaused { get; }
2929

30+
/// <summary>
31+
/// Advance the clock by the specified amount of time.
32+
/// </summary>
33+
/// <remarks>
34+
/// Used to simulate blocking I/O delays
35+
/// </remarks>
36+
void Delay(TimeSpan timeSpan);
37+
3038
/// <summary>
3139
/// Called when the emulator is paused.
3240
/// </summary>
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace Spice86.Tests.Emulator.Devices.Sound;
2+
3+
using FluentAssertions;
4+
5+
using NSubstitute;
6+
7+
using Spice86.Core.Emulator.CPU;
8+
using Spice86.Core.Emulator.Devices.Sound;
9+
using Spice86.Core.Emulator.VM;
10+
using Spice86.Core.Emulator.VM.Clock;
11+
using Spice86.Shared.Interfaces;
12+
13+
using System;
14+
15+
using Xunit;
16+
17+
[Trait("Category", "Sound")]
18+
public class Opl3ReadDelayTest {
19+
20+
[Fact]
21+
public void Opl3ReadDelayIsAMicroSecondAndAHalf() {
22+
//Arrange
23+
State state = new(CpuModel.INTEL_80386);
24+
ILoggerService loggerService = Substitute.For<ILoggerService>();
25+
CyclesClock clock = new CyclesClock(state, 3000, null, DateTimeOffset.Now);
26+
Opl3Fm opl3fm = new(new(OplMode.Opl3, 0x220, SbMixer: true),
27+
new SoftwareMixer(Audio.Filters.AudioEngine.Dummy,
28+
new PauseHandler(loggerService)),
29+
state,
30+
clock,
31+
new Core.Emulator.IOPorts.IOPortDispatcher(new(), state, loggerService, false),
32+
false,
33+
loggerService);
34+
state.Cycles = 100;
35+
36+
//Act
37+
double before = clock.ElapsedTimeMs;
38+
_ = opl3fm.ReadByte(0x330);
39+
double after = clock.ElapsedTimeMs;
40+
41+
//Assert
42+
double diff = after - before;
43+
diff.Should().BeApproximately(1.5, 1);
44+
}
45+
}

tests/Spice86.Tests/Emulator/Devices/Sound/OplIntegrationTests.cs

Lines changed: 0 additions & 124 deletions
This file was deleted.

0 commit comments

Comments
 (0)