2019-04-03 16:01:35 +00:00
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>
2019-04-03 16:10:49 +00:00
public void Clock ( int clocksToAdd = 1 )
2019-04-03 16:01:35 +00:00
{
2019-04-03 16:12:09 +00:00
clockCounter + = clocksToAdd ;
2019-04-03 16:01:35 +00:00
}
/// <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
{
2020-01-25 04:53:45 +00:00
get = > VolumeConverterOut ( _volume ) ;
2019-04-03 16:01:35 +00:00
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
}
}