Skip to content

Commit

Permalink
Testing with ADSR envelopes, additive voice signal generator
Browse files Browse the repository at this point in the history
  • Loading branch information
hagronnestad committed Oct 15, 2023
1 parent 7c8d144 commit 80877cd
Show file tree
Hide file tree
Showing 7 changed files with 692 additions and 155 deletions.
94 changes: 64 additions & 30 deletions ComputerSystems/Commodore64/Sid/NAudioImpl/NAudioSid.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ namespace Commodore64.Sid.NAudioImpl
public class NAudioSid : SidBase
{
public event EventHandler<double> VolumeMeterUpdate;
public event EventHandler<float[]> MeteringUpdate;

private const int DESIRED_LATENCY = 50;
private WavePlayerType _wavePlayerType = WavePlayerType.DirectSoundOut;
Expand Down Expand Up @@ -40,12 +41,17 @@ public bool BypassFilters
_sgVoice3Filtered.Bypass = value;
}
}
public double VolumeMeter { get; private set; }
public float VolumeMeter { get; private set; }

private SidAdsrSampleProvider _adsrVoice1;
private SidAdsrSampleProvider _adsrVoice2;
private SidAdsrSampleProvider _adsrVoice3;

private MixingSampleProvider _mixingSampleProvider;
private MeteringSampleProvider _meteringSampleProvider;
private VolumeSampleProvider _volumeSampleProvider;


private IWavePlayer _audioOutEvent;

public NAudioSid()
Expand Down Expand Up @@ -83,9 +89,14 @@ private void Init()
_sgVoice2 = new SidSignalGenerator();
_sgVoice3 = new SidSignalGenerator();

_sgVoice1Filtered = new SidFilter(_sgVoice1);
_sgVoice2Filtered = new SidFilter(_sgVoice2);
_sgVoice3Filtered = new SidFilter(_sgVoice3);
_adsrVoice1 = new SidAdsrSampleProvider(_sgVoice1);
_adsrVoice2 = new SidAdsrSampleProvider(_sgVoice2);
_adsrVoice3 = new SidAdsrSampleProvider(_sgVoice3);

_sgVoice1Filtered = new SidFilter(_adsrVoice1);
_sgVoice2Filtered = new SidFilter(_adsrVoice2);
_sgVoice3Filtered = new SidFilter(_adsrVoice3);


_mixingSampleProvider = new MixingSampleProvider(
new List<ISampleProvider>() {
Expand All @@ -101,6 +112,7 @@ private void Init()
{
VolumeMeter = e.MaxSampleValues.FirstOrDefault();
Task.Run(() => VolumeMeterUpdate?.Invoke(this, VolumeMeter));
//MeteringUpdate?.Invoke(this, e.MaxSampleValues);
};

_volumeSampleProvider = new VolumeSampleProvider(_meteringSampleProvider);
Expand Down Expand Up @@ -170,47 +182,69 @@ private void NAudioSid_RegisterChange(object sender, SidRegister reg)
if (_sgVoice2.Gain != SidVolume) _sgVoice2.Gain = SidVolume;
if (_sgVoice3.Gain != SidVolume) _sgVoice3.Gain = SidVolume;
break;

case SidRegister.VOICE1_CONTROL_REGISTER:
_adsrVoice1.Gate(Voice1.Gate);
break;

case SidRegister.VOICE2_CONTROL_REGISTER:
_adsrVoice2.Gate(Voice2.Gate);
break;

case SidRegister.VOICE3_CONTROL_REGISTER:
_adsrVoice3.Gate(Voice3.Gate);
break;

case SidRegister.VOICE1_SUSTAIN_RELEASE:
_adsrVoice1.AttackSeconds = Voice1.AttackSeconds;
_adsrVoice1.DecaySeconds = Voice1.DecaySeconds;
_adsrVoice1.SustainLevel = Voice1.SustainLevel;
_adsrVoice1.ReleaseSeconds = Voice1.ReleaseSeconds;
break;

case SidRegister.VOICE2_SUSTAIN_RELEASE:
_adsrVoice2.AttackSeconds = Voice2.AttackSeconds;
_adsrVoice2.DecaySeconds = Voice2.DecaySeconds;
_adsrVoice2.SustainLevel = Voice2.SustainLevel;
_adsrVoice2.ReleaseSeconds = Voice2.ReleaseSeconds;
break;

case SidRegister.VOICE3_SUSTAIN_RELEASE:
_adsrVoice3.AttackSeconds = Voice3.AttackSeconds;
_adsrVoice3.DecaySeconds = Voice3.DecaySeconds;
_adsrVoice3.SustainLevel = Voice3.SustainLevel;
_adsrVoice3.ReleaseSeconds = Voice3.ReleaseSeconds;
break;
}
}

private void UpdateSignalGeneratorFromVoice(SidSignalGenerator sg, Voice v)
{
// Set gain to 0 if voice is disabled or gate is low
if (!v.Gate || v.Disabled)
////Set gain to 0 if voice is disabled or gate is low
//if (!v.Gate)
//{
// //sg.Gain = 0f; // if (sg.Gain != 0) sg.Gain = 0;
//}
//else
if (v.Disabled)
{
if (sg.Gain != 0) sg.Gain = 0;
sg.Gain = 0f; // if (sg.Gain != 0) sg.Gain = 0;
}
else
{
if (sg.Gain != SidVolume) sg.Gain = SidVolume;
sg.Gain = SidVolume; // if (sg.Gain != SidVolume) sg.Gain = SidVolume;
}

// Update frequency
if (sg.Frequency != v.Frequency) sg.Frequency = v.Frequency;
sg.Frequency = v.Frequency;

// Update pulse width
if (sg.PulseWidth != v.PulseWidth) sg.PulseWidth = v.PulseWidth;

// Update waveform
switch (v.WaveForm)
{
case VoiceWaveForm.Triangle:
if (sg.Type != SidSignalGeneratorType.Triangle) sg.Type = SidSignalGeneratorType.Triangle;
break;
case VoiceWaveForm.SawTooth:
if (sg.Type != SidSignalGeneratorType.SawTooth) sg.Type = SidSignalGeneratorType.SawTooth;
break;
case VoiceWaveForm.Square:
if (sg.Type != SidSignalGeneratorType.SquarePulseWidth) sg.Type = SidSignalGeneratorType.SquarePulseWidth;
break;
case VoiceWaveForm.Noise:
if (sg.Type != SidSignalGeneratorType.White) sg.Type = SidSignalGeneratorType.White;
break;
default:
sg.Gain = 0f;
break;
}
sg.PulseWidth = v.PulseWidth;

sg.WaveformSquareActive = v.WaveformSquareActive;
sg.WaveformTriangleActive = v.WaveformTriangleActive;
sg.WaveformSawToothActive = v.WaveformSawToothActive;
sg.WaveformNoiseActive = v.WaveformNoiseActive;

}
}
Expand Down
129 changes: 129 additions & 0 deletions ComputerSystems/Commodore64/Sid/NAudioImpl/SidAdsrSampleProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using NAudio.Wave;
using System;

namespace Commodore64.Sid.NAudioImpl
{
public class SidAdsrSampleProvider : ISampleProvider
{
private readonly ISampleProvider source;
private int position;
private readonly int sampleRate;
private bool gate;
private float lastLevel;

private int attackSamples, decaySamples, releaseSamples;
private float sustainLevel;

public float AttackSeconds
{
get => attackSamples / (float)sampleRate;
set => attackSamples = (int)(sampleRate * value);
}

public float DecaySeconds
{
get => decaySamples / (float)sampleRate;
set => decaySamples = (int)(sampleRate * value);
}

public float SustainLevel
{
get => sustainLevel;
set => sustainLevel = value;
}

public float ReleaseSeconds
{
get => releaseSamples / (float)sampleRate;
set => releaseSamples = (int)(sampleRate * value);
}


public SidAdsrSampleProvider(ISampleProvider source)
{
this.source = source;
sampleRate = source.WaveFormat.SampleRate;
}

public void Gate(bool isOpen)
{
gate = isOpen;
if (!gate) // If gate is closed, it's the start of the Release phase
{
position = 0;
}
}

public WaveFormat WaveFormat => source.WaveFormat;

public int Read(float[] buffer, int offset, int count)
{
int sourceSamplesRead = source.Read(buffer, offset, count);

for (int n = 0; n < sourceSamplesRead; n++)
{
float envMultiplier = GetEnvelopeMultiplier(position++);
buffer[offset + n] *= envMultiplier;
}

return sourceSamplesRead;
}

private float GetEnvelopeMultiplier(int position)
{
if (gate) // Attack and Decay
{
if (position < attackSamples)
{
return lastLevel = (float)position / attackSamples; // Linear Attack
}

int decayPosition = position - attackSamples;
if (decayPosition < decaySamples)
{
float decayRate = (sustainLevel - lastLevel) / decaySamples; // Calculate the rate of decay
return lastLevel += decayRate; // Linear Decay
}

return lastLevel = sustainLevel; // Sustain
}
else // Release
{
if (position < releaseSamples)
{
float releaseRate = lastLevel / releaseSamples; // Calculate the rate of release
return lastLevel -= releaseRate; // Linear Release
}
return 0f;
}
}

private float GetEnvelopeMultiplierExponential(int position)
{
if (gate) // Attack and Decay
{
if (position < attackSamples)
{
return lastLevel = (float)Math.Pow(position / (double)attackSamples, 0.3f); // Exponential Attack
}

int decayPosition = position - attackSamples;
if (decayPosition < decaySamples)
{
return lastLevel *= (float)Math.Pow(sustainLevel / lastLevel, 1.0f / decaySamples); // Exponential Decay
}

return lastLevel = sustainLevel; // Sustain
}
else // Release
{
if (position < releaseSamples)
{
float releaseFactor = 1.0f - (position / (float)releaseSamples);
return lastLevel *= (float)Math.Pow(releaseFactor, 0.03f); // Exponential Release
}
return 0f;
}
}
}
}
109 changes: 109 additions & 0 deletions ComputerSystems/Commodore64/Sid/NAudioImpl/SidAdsrSampleProvider2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using NAudio.Wave;
using System;

namespace Commodore64.Sid.NAudioImpl
{
public class SidAdsrSampleProvider2 : ISampleProvider
{
private readonly ISampleProvider source;

private readonly SidEnvelopeGenerator adsr;

private float attackSeconds;
private float decaySeconds;
private float sustainLevel;
private float releaseSeconds;


public float AttackSeconds
{
get
{
return attackSeconds;
}
set
{
attackSeconds = value;
adsr.AttackRate = attackSeconds * WaveFormat.SampleRate;
}
}

public float DecaySeconds
{
get
{
return decaySeconds;
}
set
{
decaySeconds = value;
adsr.DecayRate = decaySeconds * WaveFormat.SampleRate;
}
}

public float SustainLevel
{
get
{
return sustainLevel;
}
set
{
sustainLevel = value;
adsr.SustainLevel = sustainLevel;
}
}

public float ReleaseSeconds
{
get
{
return releaseSeconds;
}
set
{
releaseSeconds = value;
adsr.ReleaseRate = releaseSeconds * WaveFormat.SampleRate;
}
}

public WaveFormat WaveFormat => source.WaveFormat;

public SidAdsrSampleProvider2(ISampleProvider source)
{
if (source.WaveFormat.Channels > 1)
{
throw new ArgumentException("Currently only supports mono inputs");
}

this.source = source;
adsr = new SidEnvelopeGenerator();
AttackSeconds = 0.01f;
adsr.SustainLevel = 1f;
adsr.DecayRate = 0f * WaveFormat.SampleRate;
ReleaseSeconds = 0.3f;
adsr.Gate(gate: true);
}

public int Read(float[] buffer, int offset, int count)
{
if (adsr.State == SidEnvelopeGenerator.EnvelopeState.Idle)
{
return 0;
}

int num = source.Read(buffer, offset, count);
for (int i = 0; i < num; i++)
{
buffer[offset++] *= adsr.Process();
}

return num;
}

public void Gate(bool gate)
{
adsr.Gate(gate);
}
}
}
Loading

0 comments on commit 80877cd

Please sign in to comment.