Skip to content
Closed
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
6 changes: 6 additions & 0 deletions src/Spice86.Core/Emulator/Devices/Sound/Opl3Fm.cs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,12 @@ public override byte ReadByte(ushort port) {
}

private byte PortRead(ushort port) {
// Some tests revealed it taking 1.5us to read an AdLib port.
// Expected by a lot of OPL music drivers in their startup routine, such as the one from 'Day of the Tentacle'
// Without this the driver decides the OPL can't be used and no music is played.
// Unlike the SB DSP reset delay which produces another (expected) response in SB port read path in the mean time,
// this wait is blocking.
_clock.Delay(TimeSpan.FromMicroseconds(1.5));
switch (_mode) {
case OplMode.Opl2:
// We allocated 4 ports, so just return -1 for the higher ones.
Expand Down
3 changes: 3 additions & 0 deletions src/Spice86.Core/Emulator/VM/Clock/ClockBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,7 @@ protected virtual void OnResumeCore() { }
/// Called by <see cref="Dispose"/> before finalization is suppressed. Override to release additional resources.
/// </summary>
protected virtual void OnDisposeCore() { }

/// <inheritdoc />
public abstract void Delay(TimeSpan timeSpan);
}
8 changes: 7 additions & 1 deletion src/Spice86.Core/Emulator/VM/Clock/CyclesClock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
/// </summary>
public class CyclesClock : ClockBase {
private readonly State _cpuState;
private TimeSpan _delay;

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

/// <inheritdoc/>
public override double ElapsedTimeMs =>
(double)_cpuState.Cycles * 1000 / CyclesPerSecond + _jitter.Advance();
(double)_cpuState.Cycles * 1000 / CyclesPerSecond + _jitter.Advance() + _delay.Microseconds;

/// <inheritdoc/>
public override void Delay(TimeSpan timeSpan) {
_delay += timeSpan;
}
}
8 changes: 7 additions & 1 deletion src/Spice86.Core/Emulator/VM/Clock/EmulatedClock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
public class EmulatedClock : ClockBase {
private int _ticks;
private readonly Stopwatch _stopwatch = new();
private TimeSpan _delay;

public EmulatedClock(int? jitterSeed, DateTimeOffset startTime)
: base(ClockJitter.Create(jitterSeed), startTime) {
Expand All @@ -21,7 +22,7 @@ public override double ElapsedTimeMs {
if (_ticks++ % 100 != 0) {
return field;
}
field = _stopwatch.Elapsed.TotalMilliseconds + _jitter.Advance();
field = _stopwatch.Elapsed.TotalMilliseconds + _jitter.Advance() + _delay.Microseconds;
return field;
}
}
Expand All @@ -34,4 +35,9 @@ public override double ElapsedTimeMs {

/// <inheritdoc/>
protected override void OnDisposeCore() => _stopwatch.Stop();

/// <inheritdoc/>
public override void Delay(TimeSpan timeSpan) {
_delay += timeSpan;
}
}
8 changes: 8 additions & 0 deletions src/Spice86.Core/Emulator/VM/Clock/IEmulatedClock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,14 @@ public interface IEmulatedClock : IDisposable {
/// </summary>
bool IsPaused { get; }

/// <summary>
/// Advance the clock by the specified amount of time.
/// </summary>
/// <remarks>
/// Used to simulate blocking I/O delays
/// </remarks>
void Delay(TimeSpan timeSpan);

/// <summary>
/// Called when the emulator is paused.
/// </summary>
Expand Down
45 changes: 45 additions & 0 deletions tests/Spice86.Tests/Emulator/Devices/Sound/Opl3ReadDelayTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
namespace Spice86.Tests.Emulator.Devices.Sound;

using FluentAssertions;

using NSubstitute;

using Spice86.Core.Emulator.CPU;
using Spice86.Core.Emulator.Devices.Sound;
using Spice86.Core.Emulator.VM;
using Spice86.Core.Emulator.VM.Clock;
using Spice86.Shared.Interfaces;

using System;

using Xunit;

[Trait("Category", "Sound")]
public class Opl3ReadDelayTest {

[Fact]
public void Opl3ReadDelayIsAMicroSecondAndAHalf() {
//Arrange
State state = new(CpuModel.INTEL_80386);
ILoggerService loggerService = Substitute.For<ILoggerService>();
CyclesClock clock = new CyclesClock(state, 3000, null, DateTimeOffset.Now);
Opl3Fm opl3fm = new(new(OplMode.Opl3, 0x220, SbMixer: true),
new SoftwareMixer(Audio.Filters.AudioEngine.Dummy,
new PauseHandler(loggerService)),
state,
clock,
new Core.Emulator.IOPorts.IOPortDispatcher(new(), state, loggerService, false),
false,
loggerService);
state.Cycles = 100;

//Act
double before = clock.ElapsedTimeMs;
_ = opl3fm.ReadByte(0x330);
double after = clock.ElapsedTimeMs;

//Assert
double diff = after - before;
diff.Should().BeApproximately(1.5, 1);
}
}
124 changes: 0 additions & 124 deletions tests/Spice86.Tests/Emulator/Devices/Sound/OplIntegrationTests.cs

This file was deleted.

Loading
Loading