Skip to content

Commit

Permalink
refactor: OPL class as a front to NukedOPL3
Browse files Browse the repository at this point in the history
  • Loading branch information
maximilien-noal committed Feb 17, 2025
1 parent 8844859 commit 9a0a884
Show file tree
Hide file tree
Showing 5 changed files with 81 additions and 101 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ public SoundBlaster(IOPortDispatcher ioPortDispatcher, SoftwareMixer softwareMix
Name = nameof(SoundBlaster),
};
PCMSoundChannel = softwareMixer.CreateChannel(nameof(SoundBlaster));
FMSynthSoundChannel = softwareMixer.CreateChannel(nameof(OPL3FM));
FMSynthSoundChannel = softwareMixer.CreateChannel(nameof(OPLFMChip));
_ctMixer = new HardwareMixer(soundBlasterHardwareConfig, PCMSoundChannel, FMSynthSoundChannel, loggerService);
InitPortHandlers(ioPortDispatcher);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public enum Mode {
public struct Control {
public Control() { }
public byte Index { get; set; }
public byte LVol { get; set; } = Opl.DefaultVolumeValue;
public byte LVol { get; set; } = OplFmSynth.DefaultVolumeValue;
public byte RVol { get; set; }

public bool IsActive { get; set; }
Expand Down Expand Up @@ -184,11 +184,9 @@ public byte Read() {
/// <summary>
/// The OPL3 / OPL2 / Adlib Gold OPL chip emulation class.
/// </summary>
public class Opl : DefaultIOPortHandler {
public class OplFmSynth {
public const byte DefaultVolumeValue = 0xff;

//public MixerChannel Channel { get; private set; } = new();

/// <summary>
/// The cache for 2 chips or an OPL3
/// </summary>
Expand Down Expand Up @@ -232,40 +230,14 @@ public Reg() {

private bool _dualOpl = false;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note

Field '_dualOpl' can be 'readonly'.

public Opl(State state, Timer timer, IOPortDispatcher ioPortDispatcher,
bool failOnUnhandledPort, ILoggerService loggerService,
AdlibGold adlibGold, OplMode oplMode)
: base(state, failOnUnhandledPort, loggerService) {
public OplFmSynth(Timer timer,
AdlibGold adlibGold, OplMode oplMode) {
_adlibGold = adlibGold;
_oplMode = oplMode;
_chip[0] = new Chip(timer);
_chip[1] = new Chip(timer);
InitPortHandlers(ioPortDispatcher);
}

public override byte ReadByte(ushort port) {
return _chip[0].Read();
}

public override void WriteByte(ushort port, byte value) {
_chip[0].Write(port, value);
}

private void InitPortHandlers(IOPortDispatcher ioPortDispatcher) {
ioPortDispatcher.AddIOPortHandler(0x388, this);
ioPortDispatcher.AddIOPortHandler(0x38b, this);
if (_dualOpl) {
//Read/Write
ioPortDispatcher.AddIOPortHandler(0x220, this);
//Read/Write
ioPortDispatcher.AddIOPortHandler(0x223, this);
}
//Read/Write
ioPortDispatcher.AddIOPortHandler(0x228, this);
//Write
ioPortDispatcher.AddIOPortHandler(0x229, this);
}

private void AdlibGoldControlWrite(byte val) {
switch (_ctrl.Index) {
case 0x04:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,54 +8,62 @@
using System;

/// <summary>
/// Virtual device which emulates OPL3 FM sound.
/// The class responsible for emulating the OPL FM music synth chip.
/// </summary>
public class OPL3FM : DefaultIOPortHandler, IDisposable {
public class OPLFMChip : DefaultIOPortHandler, IDisposable {
private const byte Timer1Mask = 0xC0;
private const byte Timer2Mask = 0xA0;

private bool _dualOpl = false;

Check notice

Code scanning / CodeQL

Missed 'readonly' opportunity Note

Field '_dualOpl' can be 'readonly'.

private readonly SoundChannel _soundChannel;
//TODO: replace it with nukedOpl3.
private readonly dynamic? _synth;
private readonly IPauseHandler _pauseHandler;
private int _currentAddress;
private volatile bool _endThread;
private readonly Thread _playbackThread;
private bool _initialized;
private byte _statusByte;
private byte _timer1Data;
private byte _timer2Data;
private byte _timerControlByte;

private bool _disposed;

/// <summary>
/// The sound channel used for the OPL3 FM synth.
/// The sound channel used for rendering audio.
/// </summary>
public SoundChannel SoundChannel => _soundChannel;

/// <summary>
/// Initializes a new instance of the OPL3 FM synth chip.
/// Initializes a new instance of the OPL FM synth chip.
/// </summary>
/// <param name="fmSynthSoundChannel">The software mixer's sound channel for the OPL3 FM Synth chip.</param>
/// <param name="fmSynthSoundChannel">The software mixer's sound channel for the OPL FM Synth chip.</param>
/// <param name="state">The CPU registers and flags.</param>
/// <param name="ioPortDispatcher">The class that is responsible for dispatching ports reads and writes to classes that respond to them.</param>
/// <param name="failOnUnhandledPort">Whether we throw an exception when an I/O port wasn't handled.</param>
/// <param name="loggerService">The logger service implementation.</param>
/// <param name="pauseHandler">Class for handling pausing the emulator.</param>
public OPL3FM(SoundChannel fmSynthSoundChannel, State state, IOPortDispatcher ioPortDispatcher, bool failOnUnhandledPort, ILoggerService loggerService, IPauseHandler pauseHandler) : base(state, failOnUnhandledPort, loggerService) {
public OPLFMChip(SoundChannel fmSynthSoundChannel, State state, IOPortDispatcher ioPortDispatcher, bool failOnUnhandledPort, ILoggerService loggerService, IPauseHandler pauseHandler) : base(state, failOnUnhandledPort, loggerService) {
_pauseHandler = pauseHandler;
_soundChannel = fmSynthSoundChannel;
//_synth = new(48000);
_playbackThread = new Thread(GenerateWaveforms) {
Name = nameof(OPL3FM)
Name = nameof(OPLFMChip)
};
InitPortHandlers(ioPortDispatcher);
StartPlaybackThread();
}


private void InitPortHandlers(IOPortDispatcher ioPortDispatcher) {
ioPortDispatcher.AddIOPortHandler(0x388, this);
ioPortDispatcher.AddIOPortHandler(0x389, this);
ioPortDispatcher.AddIOPortHandler(0x38b, this);
if (_dualOpl) {
//Read/Write
ioPortDispatcher.AddIOPortHandler(0x220, this);
//Read/Write
ioPortDispatcher.AddIOPortHandler(0x223, this);
}
//Read/Write
ioPortDispatcher.AddIOPortHandler(0x228, this);
//Write
ioPortDispatcher.AddIOPortHandler(0x229, this);
}

/// <inheritdoc />
Expand All @@ -72,66 +80,67 @@ private void Dispose(bool disposing) {
if (_playbackThread.IsAlive) {
_playbackThread.Join();
}
_initialized = false;
}
_disposed = true;
}
}

/// <inheritdoc />
public override byte ReadByte(ushort port) {
if ((_timerControlByte & 0x01) != 0x00 && (_statusByte & Timer1Mask) == 0) {
_timer1Data++;
if (_timer1Data == 0) {
_statusByte |= Timer1Mask;
}
}

if ((_timerControlByte & 0x02) != 0x00 && (_statusByte & Timer2Mask) == 0) {
_timer2Data++;
if (_timer2Data == 0) {
_statusByte |= Timer2Mask;
}
}

return _statusByte;
//if ((_timerControlByte & 0x01) != 0x00 && (_statusByte & Timer1Mask) == 0) {
// _timer1Data++;
// if (_timer1Data == 0) {
// _statusByte |= Timer1Mask;
// }
//}

//if ((_timerControlByte & 0x02) != 0x00 && (_statusByte & Timer2Mask) == 0) {
// _timer2Data++;
// if (_timer2Data == 0) {
// _statusByte |= Timer2Mask;
// }
//}

//return _statusByte;
throw new NotImplementedException();
}

/// <inheritdoc />
public override ushort ReadWord(ushort port) {
return _statusByte;
//return _statusByte;
throw new NotImplementedException();
}

/// <inheritdoc />
public override void WriteByte(ushort port, byte value) {
if (port == 0x388) {
_currentAddress = value;
} else if (port == 0x389) {
if (_currentAddress == 0x02) {
_timer1Data = value;
} else if (_currentAddress == 0x03) {
_timer2Data = value;
} else if (_currentAddress == 0x04) {
_timerControlByte = value;
if ((value & 0x80) == 0x80) {
_statusByte = 0;
}
} else {
if (!_initialized) {
StartPlaybackThread();
}

_synth?.SetRegisterValue(0, _currentAddress, value);
}
}
//if (port == 0x388) {
// _currentAddress = value;
//} else if (port == 0x389) {
// if (_currentAddress == 0x02) {
// _timer1Data = value;
// } else if (_currentAddress == 0x03) {
// _timer2Data = value;
// } else if (_currentAddress == 0x04) {
// _timerControlByte = value;
// if ((value & 0x80) == 0x80) {
// _statusByte = 0;
// }
// } else {
// if (!_initialized) {
// StartPlaybackThread();
// }

// _synth?.SetRegisterValue(0, _currentAddress, value);
// }
//}
}

/// <inheritdoc />
public override void WriteWord(ushort port, ushort value) {
if (port == 0x388) {
WriteByte(0x388, (byte)value);
WriteByte(0x389, (byte)(value >> 8));
}
//if (port == 0x388) {
// WriteByte(0x388, (byte)value);
// WriteByte(0x389, (byte)(value >> 8));
//}
}
/// <summary>
/// Generates and plays back output waveform data.
Expand All @@ -140,25 +149,21 @@ private void GenerateWaveforms() {
const int length = 1024;
Span<float> buffer = stackalloc float[length];
Span<float> playBuffer = stackalloc float[length * 2];
FillBuffer(buffer, playBuffer);
MonoToStereo(buffer, playBuffer);
while (!_endThread) {
_pauseHandler.WaitIfPaused();
_soundChannel.Render(playBuffer);
FillBuffer(buffer, playBuffer);
MonoToStereo(buffer, playBuffer);
}
}

private void FillBuffer(Span<float> buffer, Span<float> playBuffer) {
private void MonoToStereo(Span<float> buffer, Span<float> playBuffer) {
//_synth?.GetData(buffer);
ChannelAdapter.MonoToStereo(buffer, playBuffer);
}

private void StartPlaybackThread() {
if (_endThread) {
return;
}
_loggerService.Information("Starting thread '{ThreadName}'", _playbackThread.Name ?? nameof(OPL3FM));
_initialized = true;
_loggerService.Information("Starting thread '{ThreadName}'", _playbackThread.Name ?? nameof(OPLFMChip));
_playbackThread.Start();
}
}
5 changes: 3 additions & 2 deletions src/Spice86.Core/Emulator/VM/Machine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
using Spice86.Core.Emulator.Devices.Sound.Blaster;
using Spice86.Core.Emulator.Devices.Sound.Midi;
using Spice86.Core.Emulator.Devices.Sound.PCSpeaker;
using Spice86.Core.Emulator.Devices.Sound.Ymf262Emu;
using Spice86.Core.Emulator.Devices.Timer;
using Spice86.Core.Emulator.Devices.Video;
using Spice86.Core.Emulator.InterruptHandlers.Bios;
Expand Down Expand Up @@ -189,7 +190,7 @@ public sealed class Machine : IDisposable {
/// <summary>
/// The OPL3 FM Synth chip.
/// </summary>
public Opl OPL { get; }
public OPLFMChip OPL { get; }

/// <summary>
/// The internal software mixer for all sound channels.
Expand Down Expand Up @@ -224,7 +225,7 @@ public sealed class Machine : IDisposable {
/// <summary>
/// Initializes a new instance of the <see cref="Machine"/> class.
/// </summary>
public Machine(BiosDataArea biosDataArea, BiosEquipmentDeterminationInt11Handler biosEquipmentDeterminationInt11Handler, BiosKeyboardInt9Handler biosKeyboardInt9Handler, CallbackHandler callbackHandler, Cpu cpu, CfgCpu cfgCpu, State cpuState, Dos dos, GravisUltraSound gravisUltraSound, IOPortDispatcher ioPortDispatcher, Joystick joystick, Keyboard keyboard, KeyboardInt16Handler keyboardInt16Handler, EmulatorBreakpointsManager emulatorBreakpointsManager, IMemory memory, Midi midiDevice, PcSpeaker pcSpeaker, DualPic dualPic, SoundBlaster soundBlaster, SystemBiosInt12Handler systemBiosInt12Handler, SystemBiosInt15Handler systemBiosInt15Handler, SystemClockInt1AHandler systemClockInt1AHandler, Timer timer, TimerInt8Handler timerInt8Handler, VgaCard vgaCard, IVideoState vgaRegisters, IIOPortHandler vgaIoPortHandler, IVgaRenderer vgaRenderer, IVideoInt10Handler videoInt10Handler, VgaRom vgaRom, DmaController dmaController, Opl opl, SoftwareMixer softwareMixer, IMouseDevice mouseDevice, IMouseDriver mouseDriver, IVgaFunctionality vgaFunctions, IPauseHandler pauseHandler) {
public Machine(BiosDataArea biosDataArea, BiosEquipmentDeterminationInt11Handler biosEquipmentDeterminationInt11Handler, BiosKeyboardInt9Handler biosKeyboardInt9Handler, CallbackHandler callbackHandler, Cpu cpu, CfgCpu cfgCpu, State cpuState, Dos dos, GravisUltraSound gravisUltraSound, IOPortDispatcher ioPortDispatcher, Joystick joystick, Keyboard keyboard, KeyboardInt16Handler keyboardInt16Handler, EmulatorBreakpointsManager emulatorBreakpointsManager, IMemory memory, Midi midiDevice, PcSpeaker pcSpeaker, DualPic dualPic, SoundBlaster soundBlaster, SystemBiosInt12Handler systemBiosInt12Handler, SystemBiosInt15Handler systemBiosInt15Handler, SystemClockInt1AHandler systemClockInt1AHandler, Timer timer, TimerInt8Handler timerInt8Handler, VgaCard vgaCard, IVideoState vgaRegisters, IIOPortHandler vgaIoPortHandler, IVgaRenderer vgaRenderer, IVideoInt10Handler videoInt10Handler, VgaRom vgaRom, DmaController dmaController, OPLFMChip opl, SoftwareMixer softwareMixer, IMouseDevice mouseDevice, IMouseDriver mouseDriver, IVgaFunctionality vgaFunctions, IPauseHandler pauseHandler) {
BiosDataArea = biosDataArea;
BiosEquipmentDeterminationInt11Handler = biosEquipmentDeterminationInt11Handler;
BiosKeyboardInt9Handler = biosKeyboardInt9Handler;
Expand Down
8 changes: 5 additions & 3 deletions src/Spice86/Spice86DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace Spice86;
using Spice86.Core.Emulator.Devices.Sound.Blaster;
using Spice86.Core.Emulator.Devices.Sound.Midi;
using Spice86.Core.Emulator.Devices.Sound.PCSpeaker;
using Spice86.Core.Emulator.Devices.Sound.Ymf262Emu;
using Spice86.Core.Emulator.Devices.Timer;
using Spice86.Core.Emulator.Devices.Video;
using Spice86.Core.Emulator.Function;
Expand Down Expand Up @@ -191,9 +192,10 @@ public Spice86DependencyInjection(ILoggerService loggerService, Configuration co
timer,
timerInt8Handler,
vgaCard, videoState, videoInt10Handler, vgaRenderer, vgaBios, vgaRom,
dmaController, new Opl(
state, timer, ioPortDispatcher, configuration.FailOnUnhandledPort, loggerService,
new AdlibGold(loggerService), OplMode.Opl3),
dmaController, new OPLFMChip(
soundBlaster.FMSynthSoundChannel, state,
ioPortDispatcher, configuration.FailOnUnhandledPort,
loggerService, pauseHandler),
softwareMixer, mouse, mouseDriver,
vgaFunctionality, pauseHandler);

Expand Down

0 comments on commit 9a0a884

Please sign in to comment.