Browse Source

Merge branch 'compressorsampleprovider' into netstandard

pull/367/head
Mark Heath 7 years ago
parent
commit
1493cf0c95
  1. 33
      NAudio/Dsp/EnvelopeDetector.cs
  2. 28
      NAudio/Dsp/SimpleCompressor.cs
  3. 7
      NAudio/Dsp/SimpleGate.cs
  4. 183
      NAudio/Wave/WaveStreams/SimpleCompressorStream.cs

33
NAudio/Dsp/EnvelopeDetector.cs

@ -24,10 +24,7 @@ namespace NAudio.Dsp
public double TimeConstant
{
get
{
return ms;
}
get => ms;
set
{
System.Diagnostics.Debug.Assert( value > 0.0 );
@ -38,10 +35,7 @@ namespace NAudio.Dsp
public double SampleRate
{
get
{
return sampleRate;
}
get => sampleRate;
set
{
System.Diagnostics.Debug.Assert( value > 0.0 );
@ -50,9 +44,9 @@ namespace NAudio.Dsp
}
}
public void Run( double inValue, ref double state )
public double Run( double inValue, double state )
{
state = inValue + coeff * (state - inValue);
return inValue + coeff * (state - inValue);
}
private void SetCoef()
@ -77,32 +71,29 @@ namespace NAudio.Dsp
public double Attack
{
get { return attack.TimeConstant; }
set { attack.TimeConstant = value; }
get => attack.TimeConstant;
set => attack.TimeConstant = value;
}
public double Release
{
get { return release.TimeConstant; }
set { release.TimeConstant = value; }
get => release.TimeConstant;
set => release.TimeConstant = value;
}
public double SampleRate
{
get { return attack.SampleRate; }
set { attack.SampleRate = release.SampleRate = value; }
get => attack.SampleRate;
set => attack.SampleRate = release.SampleRate = value;
}
public void Run(double inValue, ref double state)
public double Run(double inValue, double state)
{
// assumes that:
// positive delta = attack
// negative delta = release
// good for linear & log values
if ( inValue > state )
attack.Run( inValue, ref state ); // attack
else
release.Run( inValue, ref state ); // release
return inValue > state ? attack.Run( inValue, state ) : release.Run( inValue, state );
}
}
}

28
NAudio/Dsp/SimpleCompressor.cs

@ -6,10 +6,8 @@ namespace NAudio.Dsp
{
class SimpleCompressor : AttRelEnvelope
{
// transfer function
// runtime variables
private double envdB; // over-threshold envelope (dB)
private double envdB; // over-threshold envelope (dB)
public SimpleCompressor(double attackTime, double releaseTime, double sampleRate)
: base(attackTime, releaseTime, sampleRate)
@ -21,12 +19,8 @@ namespace NAudio.Dsp
}
public SimpleCompressor()
: base(10.0, 10.0, 44100.0)
: this(10.0, 10.0, 44100.0)
{
this.Threshold = 0.0;
this.Ratio = 1.0;
this.MakeUpGain = 0.0;
this.envdB = DC_OFFSET;
}
public double MakeUpGain { get; set; }
@ -47,29 +41,29 @@ namespace NAudio.Dsp
// sidechain
// rectify input
double rect1 = Math.Abs( in1 ); // n.b. was fabs
double rect2 = Math.Abs( in2 ); // n.b. was fabs
double rect1 = Math.Abs(in1); // n.b. was fabs
double rect2 = Math.Abs(in2); // n.b. was fabs
// if desired, one could use another EnvelopeDetector to smooth
// the rectified signal.
double link = Math.Max( rect1, rect2 ); // link channels with greater of 2
link += DC_OFFSET; // add DC offset to avoid log( 0 )
double keydB = Decibels.LinearToDecibels( link ); // convert linear -> dB
link += DC_OFFSET; // add DC offset to avoid log( 0 )
double keydB = Decibels.LinearToDecibels(link); // convert linear -> dB
// threshold
double overdB = keydB - Threshold; // delta over threshold
if ( overdB < 0.0 )
double overdB = keydB - Threshold; // delta over threshold
if (overdB < 0.0)
overdB = 0.0;
// attack/release
overdB += DC_OFFSET; // add DC offset to avoid denormal
overdB += DC_OFFSET; // add DC offset to avoid denormal
Run( overdB, ref envdB ); // run attack/release envelope
envdB = Run(overdB, envdB); // run attack/release envelope
overdB = envdB - DC_OFFSET; // subtract DC offset
overdB = envdB - DC_OFFSET; // subtract DC offset
// Regarding the DC offset: In this case, since the offset is added before
// the attack/release processes, the envelope will never fall below the offset,

7
NAudio/Dsp/SimpleGate.cs

@ -42,7 +42,7 @@ namespace NAudio.Dsp
// attack/release
over += DC_OFFSET; // add DC offset to avoid denormal
Run( over, ref env ); // run attack/release
env = Run(over, env); // run attack/release
over = env - DC_OFFSET; // subtract DC offset
@ -59,10 +59,7 @@ namespace NAudio.Dsp
public double Threshold
{
get
{
return threshdB;
}
get => threshdB;
set
{
threshdB = value;

183
NAudio/Wave/WaveStreams/SimpleCompressorStream.cs

@ -1,4 +1,3 @@
using System;
using NAudio.Dsp;
namespace NAudio.Wave
@ -6,24 +5,21 @@ namespace NAudio.Wave
/// <summary>
/// A simple compressor
/// </summary>
public class SimpleCompressorStream : WaveStream
public class SimpleCompressorEffect : ISampleProvider
{
private WaveStream sourceStream;
private readonly ISampleProvider sourceStream;
private readonly SimpleCompressor simpleCompressor;
private byte[] sourceBuffer; // buffer used by Read function
private readonly int channels;
private readonly int bytesPerSample;
private readonly object lockObject = new object();
/// <summary>
/// Create a new simple compressor stream
/// </summary>
/// <param name="sourceStream">Source stream</param>
public SimpleCompressorStream(WaveStream sourceStream)
public SimpleCompressorEffect(ISampleProvider sourceStream)
{
this.sourceStream = sourceStream;
channels = sourceStream.WaveFormat.Channels;
bytesPerSample = sourceStream.WaveFormat.BitsPerSample / 8;
simpleCompressor = new SimpleCompressor(5.0, 10.0, sourceStream.WaveFormat.SampleRate);
simpleCompressor.Threshold = 16;
simpleCompressor.Ratio = 6;
@ -36,10 +32,7 @@ namespace NAudio.Wave
/// </summary>
public double MakeUpGain
{
get
{
return simpleCompressor.MakeUpGain;
}
get => simpleCompressor.MakeUpGain;
set
{
lock (lockObject)
@ -54,10 +47,7 @@ namespace NAudio.Wave
/// </summary>
public double Threshold
{
get
{
return simpleCompressor.Threshold;
}
get => simpleCompressor.Threshold;
set
{
lock (lockObject)
@ -72,10 +62,7 @@ namespace NAudio.Wave
/// </summary>
public double Ratio
{
get
{
return simpleCompressor.Ratio;
}
get => simpleCompressor.Ratio;
set
{
lock (lockObject)
@ -90,10 +77,7 @@ namespace NAudio.Wave
/// </summary>
public double Attack
{
get
{
return simpleCompressor.Attack;
}
get => simpleCompressor.Attack;
set
{
lock (lockObject)
@ -108,10 +92,7 @@ namespace NAudio.Wave
/// </summary>
public double Release
{
get
{
return simpleCompressor.Release;
}
get => simpleCompressor.Release;
set
{
lock (lockObject)
@ -121,103 +102,16 @@ namespace NAudio.Wave
}
}
/// <summary>
/// Determine whether the stream has the required amount of data.
/// </summary>
/// <param name="count">Number of bytes of data required from the stream.</param>
/// <returns>Flag indicating whether the required amount of data is avialable.</returns>
public override bool HasData(int count)
{
return sourceStream.HasData(count);
}
/// <summary>
/// Turns gain on or off
/// </summary>
public bool Enabled { get; set; }
/// <summary>
/// Returns the stream length
/// </summary>
public override long Length => sourceStream.Length;
/// <summary>
/// Gets or sets the current position in the stream
/// </summary>
public override long Position
{
get
{
return sourceStream.Position;
}
set
{
lock (lockObject)
{
sourceStream.Position = value;
}
}
}
/// <summary>
/// Gets the WaveFormat of this stream
/// </summary>
public override WaveFormat WaveFormat => sourceStream.WaveFormat;
public WaveFormat WaveFormat => sourceStream.WaveFormat;
private void ReadSamples(byte[] buffer, int start, out double left, out double right)
{
if (bytesPerSample == 4)
{
left = BitConverter.ToSingle(buffer, start);
if (channels > 1)
{
right = BitConverter.ToSingle(buffer, start + bytesPerSample);
}
else
{
right = left;
}
}
else if (bytesPerSample == 2)
{
left = BitConverter.ToInt16(buffer, start) / 32768.0;
if (channels > 1)
{
right = BitConverter.ToInt16(buffer, start + bytesPerSample) / 32768.0;
}
else
{
right = left;
}
}
else
{
throw new InvalidOperationException(String.Format("Unsupported bytes per sample: {0}", bytesPerSample));
}
}
private void WriteSamples(byte[] buffer, int start, double left, double right)
{
if (bytesPerSample == 4)
{
Array.Copy(BitConverter.GetBytes((float)left), 0, buffer, start, bytesPerSample);
if (channels > 1)
{
Array.Copy(BitConverter.GetBytes((float)right), 0, buffer, start + bytesPerSample, bytesPerSample);
}
}
else if (bytesPerSample == 2)
{
Array.Copy(BitConverter.GetBytes((short)(left * 32768.0)), 0, buffer, start, bytesPerSample);
if (channels > 1)
{
Array.Copy(BitConverter.GetBytes((short)(right * 32768.0)), 0, buffer, start + bytesPerSample, bytesPerSample);
}
}
}
/// <summary>
/// Reads bytes from this stream
@ -226,65 +120,24 @@ namespace NAudio.Wave
/// <param name="offset">Offset in array to read into</param>
/// <param name="count">Number of bytes to read</param>
/// <returns>Number of bytes read</returns>
public override int Read(byte[] array, int offset, int count)
public int Read(float[] array, int offset, int count)
{
lock (lockObject)
{
int samplesRead = sourceStream.Read(array, offset, count);
if (Enabled)
{
if (sourceBuffer == null || sourceBuffer.Length < count)
sourceBuffer = new byte[count];
int sourceBytesRead = sourceStream.Read(sourceBuffer, 0, count);
int sampleCount = sourceBytesRead / (bytesPerSample * channels);
for (int sample = 0; sample < sampleCount; sample++)
for (int sample = 0; sample < samplesRead; sample+=channels)
{
int start = sample * bytesPerSample * channels;
double in1;
double in2;
ReadSamples(sourceBuffer, start, out in1, out in2);
double in1 = array[offset+sample];
double in2 = (channels == 1) ? 0 : array[offset+sample+1];
simpleCompressor.Process(ref in1, ref in2);
WriteSamples(array, offset + start, in1, in2);
array[offset + sample] = (float)in1;
if (channels > 1)
array[offset + sample + 1] = (float)in2;
}
return count;
}
else
{
return sourceStream.Read(array, offset, count);
}
}
}
/// <summary>
/// Disposes this stream
/// </summary>
/// <param name="disposing">true if the user called this</param>
protected override void Dispose(bool disposing)
{
if (disposing)
{
// Release managed resources.
if (sourceStream != null)
{
sourceStream.Dispose();
sourceStream = null;
}
}
// Release unmanaged resources.
// Set large fields to null.
// Call Dispose on your base class.
base.Dispose(disposing);
}
/// <summary>
/// Gets the block alignment for this stream
/// </summary>
public override int BlockAlign
{
get
{
// TODO: investigate forcing 20ms
return sourceStream.BlockAlign;
return samplesRead;
}
}
}

Loading…
Cancel
Save