namespace Jellyfish.Virtu { public interface ISpeaker { void ToggleOutput(); // ReSharper disable once UnusedMember.Global void Clear(); // ReSharper disable once UnusedMember.Global void GetSamples(out short[] samples, out int nSamp); // ReSharper disable once UnusedMember.Global void Sync(IComponentSerializer ser); } // ReSharper disable once UnusedMember.Global public sealed class Speaker : ISpeaker { private const int CyclesPerFlush = 23; private readonly MachineEvents _events; private readonly ICpu _cpu; private bool _isHigh; private int _highCycles; private int _totalCycles; private long _lastCycles; // only relevant if trying to savestate mid-frame private readonly short[] _buffer = new short[4096]; private int _position; public Speaker(MachineEvents events, ICpu cpu) { _events = events; _cpu = cpu; _events.AddEventDelegate(EventCallbacks.FlushOutput, FlushOutputEvent); _events.AddEvent(CyclesPerFlush * _cpu.Multiplier, EventCallbacks.FlushOutput); _isHigh = false; _highCycles = _totalCycles = 0; } public void Sync(IComponentSerializer ser) { ser.Sync(nameof(_isHigh), ref _isHigh); ser.Sync(nameof(_highCycles), ref _highCycles); ser.Sync(nameof(_totalCycles), ref _totalCycles); ser.Sync(nameof(_lastCycles), ref _lastCycles); } public void Clear() { _position = 0; } public void GetSamples(out short[] samples, out int nSamp) { samples = _buffer; nSamp = _position / 2; _position = 0; } public void ToggleOutput() { UpdateCycles(); _isHigh ^= true; } private void FlushOutputEvent() { UpdateCycles(); // TODO: better than simple decimation here!! Output(_highCycles * short.MaxValue / _totalCycles); _highCycles = _totalCycles = 0; _events.AddEvent(CyclesPerFlush * _cpu.Multiplier, EventCallbacks.FlushOutput); } private void UpdateCycles() { int delta = (int)(_cpu.Cycles - _lastCycles); if (_isHigh) { _highCycles += delta; } _totalCycles += delta; _lastCycles = _cpu.Cycles; } private void Output(int data) // machine thread { data = (int)(data * 0.2); if (_position < _buffer.Length - 2) { _buffer[_position++] = (short)data; _buffer[_position++] = (short)data; } } } }