190 lines
4.6 KiB
C#
190 lines
4.6 KiB
C#
using System;
|
|
using BizHawk.Common;
|
|
using BizHawk.Emulation.Common;
|
|
|
|
namespace BizHawk.Emulation.Cores.Sound
|
|
{
|
|
/// <summary>
|
|
/// A simple 1-bit (mono) beeper/buzzer implementation using blipbuffer
|
|
/// Simulating the piezzo-electric buzzer found in many old computers (such as the ZX Spectrum or Amstrad CPC)
|
|
/// Sound is generated by toggling the single input line ON and OFF rapidly
|
|
/// </summary>
|
|
public sealed class OneBitBeeper : ISoundProvider
|
|
{
|
|
private int _sampleRate;
|
|
private int _clocksPerFrame;
|
|
private int _framesPerSecond;
|
|
private BlipBuffer _blip;
|
|
private readonly string _beeperId;
|
|
|
|
/// <summary>
|
|
/// Constructor
|
|
/// </summary>
|
|
/// <param name="blipSampleRate">The sample rate to pass to blipbuffer (this should be 44100 for ISoundProvider)</param>
|
|
/// <param name="clocksPerFrame">The number of (usually CPU) clocked cycles in one frame</param>
|
|
/// <param name="framesPerSecond">The number of frames per second (usually either 60 or 50)</param>
|
|
/// <param name="beeperId">Unique name for this instance (needed for serialization as some cores have more than one active instance of the beeper)</param>
|
|
public OneBitBeeper(int blipSampleRate, int clocksPerFrame, int framesPerSecond, string beeperId)
|
|
{
|
|
_beeperId = beeperId;
|
|
_sampleRate = blipSampleRate;
|
|
_clocksPerFrame = clocksPerFrame;
|
|
_framesPerSecond = framesPerSecond;
|
|
_blip = new BlipBuffer(blipSampleRate / framesPerSecond);
|
|
_blip.SetRates(clocksPerFrame * 50, blipSampleRate);
|
|
}
|
|
|
|
private int clockCounter;
|
|
|
|
/// <summary>
|
|
/// Option to clock the beeper every CPU clock
|
|
/// </summary>
|
|
public void Clock(int clocksToAdd = 1)
|
|
{
|
|
clockCounter += clocksToAdd;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Option to directly set the current clock position within the frame
|
|
/// </summary>
|
|
public void SetClock(int currentFrameClock)
|
|
{
|
|
clockCounter = currentFrameClock;
|
|
}
|
|
|
|
private bool lastPulse;
|
|
|
|
/// <summary>
|
|
/// Processes an incoming pulse value
|
|
/// </summary>
|
|
public void ProcessPulseValue(bool pulse, bool renderSound = true)
|
|
{
|
|
if (!renderSound)
|
|
return;
|
|
|
|
if (lastPulse == pulse)
|
|
{
|
|
// no change
|
|
_blip.AddDelta((uint)clockCounter, 0);
|
|
}
|
|
|
|
else
|
|
{
|
|
if (pulse)
|
|
_blip.AddDelta((uint)clockCounter, (short)(_volume));
|
|
else
|
|
_blip.AddDelta((uint)clockCounter, -(short)(_volume));
|
|
|
|
lastVolume = _volume;
|
|
}
|
|
|
|
lastPulse = pulse;
|
|
}
|
|
|
|
#region Volume Handling
|
|
|
|
/// <summary>
|
|
/// Beeper volume
|
|
/// Accepts an int 0-100 value
|
|
/// </summary>
|
|
public int Volume
|
|
{
|
|
get => VolumeConverterOut(_volume);
|
|
set
|
|
{
|
|
var newVol = VolumeConverterIn(value);
|
|
if (newVol != _volume)
|
|
_blip.Clear();
|
|
_volume = VolumeConverterIn(value);
|
|
}
|
|
}
|
|
private int _volume;
|
|
|
|
/// <summary>
|
|
/// The last used volume (used to modify blipbuffer delta values)
|
|
/// </summary>
|
|
private int lastVolume;
|
|
|
|
|
|
/// <summary>
|
|
/// Takes an int 0-100 and returns the relevant short volume to output
|
|
/// </summary>
|
|
private int VolumeConverterIn(int vol)
|
|
{
|
|
int maxLimit = short.MaxValue / 3;
|
|
int increment = maxLimit / 100;
|
|
|
|
return vol * increment;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Takes an short volume and returns the relevant int value 0-100
|
|
/// </summary>
|
|
private int VolumeConverterOut(int shortvol)
|
|
{
|
|
int maxLimit = short.MaxValue / 3;
|
|
int increment = maxLimit / 100;
|
|
|
|
if (shortvol > maxLimit)
|
|
shortvol = maxLimit;
|
|
|
|
return shortvol / increment;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region ISoundProvider
|
|
|
|
public bool CanProvideAsync => false;
|
|
|
|
public SyncSoundMode SyncMode => SyncSoundMode.Sync;
|
|
|
|
public void SetSyncMode(SyncSoundMode mode)
|
|
{
|
|
if (mode != SyncSoundMode.Sync)
|
|
throw new InvalidOperationException("Only Sync mode is supported.");
|
|
}
|
|
|
|
public void GetSamplesAsync(short[] samples)
|
|
{
|
|
throw new NotSupportedException("Async is not available");
|
|
}
|
|
|
|
public void DiscardSamples()
|
|
{
|
|
_blip.Clear();
|
|
}
|
|
|
|
public void GetSamplesSync(out short[] samples, out int nsamp)
|
|
{
|
|
_blip.EndFrame((uint)_clocksPerFrame);
|
|
nsamp = _blip.SamplesAvailable();
|
|
samples = new short[nsamp * 2];
|
|
_blip.ReadSamples(samples, nsamp, true);
|
|
for (int i = 0; i < nsamp * 2; i += 2)
|
|
{
|
|
samples[i + 1] = samples[i];
|
|
}
|
|
|
|
clockCounter = 0;
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region State Serialization
|
|
|
|
public void SyncState(Serializer ser)
|
|
{
|
|
ser.BeginSection("Beeper_" + _beeperId);
|
|
ser.Sync(nameof(_sampleRate), ref _sampleRate);
|
|
ser.Sync(nameof(_clocksPerFrame), ref _clocksPerFrame);
|
|
ser.Sync(nameof(_framesPerSecond), ref _framesPerSecond);
|
|
ser.Sync(nameof(clockCounter), ref clockCounter);
|
|
ser.Sync(nameof(lastPulse), ref lastPulse);
|
|
ser.EndSection();
|
|
}
|
|
|
|
#endregion
|
|
}
|
|
}
|