#region License
/* Copyright (c) 2007 Leslie Sanford
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#endregion
#region Contact
/*
* Leslie Sanford
* Email: jabberdabber@hotmail.com
*/
#endregion
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using Sanford.Multimedia.Midi;
namespace Sanford.Multimedia.Synth
{
/// <summary>
/// Represents a low frequency oscillator.
/// </summary>
public class Lfo : MonoSynthComponent, IProgrammable, IControllable
{
#region Lfo Members
public enum ParameterId
{
Shape,
Speed,
Delay,
ModulationWheelEnabled
}
public enum Shape
{
Sine,
Triangle,
Square,
DownRamp,
UpRamp,
SampleAndHold
}
#region Fields
#region Constants
private const int SpeedScaler = 20;
private const int ShapeCount = (int)Shape.SampleAndHold + 1;
private const int DelayScaler = 3;
#endregion
private Random rand = new Random();
private float shOutput = 0;
private float speed;
private Shape shape;
private int delay;
private float sine = 0;
private float cosine = 1;
private float amplitude = 1;
private bool modulationWheelEnabled = false;
private int samples = 0;
private float increment;
private float accumulator = 0;
private bool synthesizeReplaceEnabled = true;
#endregion
#region Constructors
public Lfo(SampleRate sampleRate, MonoBuffer buffer) : base(sampleRate, buffer)
{
#region Require
if(sampleRate == null)
{
throw new ArgumentNullException("sampleRate");
}
else if(buffer == null)
{
throw new ArgumentNullException("buffer");
}
#endregion
sampleRate.SampleRateChanged += HandleSampleRateChanged;
InitializeParameters();
}
public Lfo(SampleRate sampleRate, MonoBuffer buffer, string name) : base(sampleRate, buffer, name)
{
#region Require
if(sampleRate == null)
{
throw new ArgumentNullException("sampleRate");
}
else if(buffer == null)
{
throw new ArgumentNullException("buffer");
}
else if(name == null)
{
throw new ArgumentNullException("name");
}
#endregion
sampleRate.SampleRateChanged += HandleSampleRateChanged;
InitializeParameters();
}
#endregion
#region Methods
private void InitializeParameters()
{
SetParameterValue((int)ParameterId.Shape, 0);
SetParameterValue((int)ParameterId.Delay, 0);
SetParameterValue((int)ParameterId.Speed, 0.2f);
}
// Calculates phase increment.
private void CalculateIncrement()
{
increment = PI2 * speed / SamplesPerSecond;
}
public override void Synthesize(int offset, int count)
{
float[] buffer = GetBuffer();
Debug.Assert(buffer.Length >= offset + count);
int start = offset;
int end = offset + count;
// While the end of the delay has not been reached and
// the start point hasn't reached the end point.
while(samples < delay && start < end)
{
if(synthesizeReplaceEnabled)
{
buffer[start] = 0;
}
samples++;
start++;
}
if(samples >= delay)
{
switch(shape)
{
case Shape.Sine:
SynthesizeSine(start, end);
break;
case Shape.Triangle:
SynthesizeTriangle(start, end);
break;
case Shape.Square:
SynthesizeSquare(start, end);
break;
case Shape.DownRamp:
SynthesizeDownRamp(start, end);
break;
case Shape.UpRamp:
SynthesizeUpRamp(start, end);
break;
case Shape.SampleAndHold:
SynthesizeSampleAndHold(start, end);
break;
default:
Debug.Fail("Unhandled shape.");
break;
}
}
// Increase sample counter by the number of samples left to synthesize.
samples += end - start;
}
public override void Trigger(int previousNote, int note, float velocity)
{
samples = 0;
sine = 0;
cosine = 1;
switch(shape)
{
case Shape.Sine:
case Shape.Square:
case Shape.SampleAndHold:
accumulator = 0;
break;
case Shape.Triangle:
accumulator = 0.25f * PI2;
break;
case Shape.DownRamp:
case Shape.UpRamp:
accumulator = (float)Math.PI;
break;
default:
Debug.Fail("Unhandled shape.");
break;
}
}
public override void Release(float velocity)
{
}
private bool IncrementPhase()
{
bool result;
sine = sine + increment * cosine;
cosine = cosine - increment * sine;
accumulator += increment;
if(accumulator >= PI2)
{
accumulator -= PI2;
result = true;
}
else
{
result = false;
}
return result;
}
private void SynthesizeSine(int start, int end)
{
float[] buffer = GetBuffer();
float output;
for(int i = start; i < end; i++)
{
output = sine * amplitude;
buffer[i] = (synthesizeReplaceEnabled ? output : buffer[i] + output);
IncrementPhase();
}
}
private void SynthesizeTriangle(int start, int end)
{
float[] buffer = GetBuffer();
float output;
for(int i = start; i < end; i++)
{
if(accumulator < Math.PI)
{
output = 1 - 4 * accumulator / PI2;
}
else
{
output = 1 - 4 * (1 - accumulator / PI2);
}
output *= amplitude;
buffer[i] = (synthesizeReplaceEnabled ? output : buffer[i] + output);
IncrementPhase();
}
}
private void SynthesizeSquare(int start, int end)
{
float[] buffer = GetBuffer();
float output;
for(int i = start; i < end; i++)
{
if(accumulator < Math.PI)
{
output = 1;
}
else
{
output = -1;
}
output *= amplitude;
buffer[i] = (synthesizeReplaceEnabled ? output : buffer[i] + output);
IncrementPhase();
}
}
private void SynthesizeDownRamp(int start, int end)
{
float[] buffer = GetBuffer();
float output;
for(int i = start; i < end; i++)
{
output = 1 - 2 * accumulator / PI2;
output *= amplitude;
buffer[i] = (synthesizeReplaceEnabled ? output : buffer[i] + output);
IncrementPhase();
}
}
private void SynthesizeUpRamp(int start, int end)
{
float[] buffer = GetBuffer();
float output;
for(int i = start; i < end; i++)
{
output = 1 - 2 * (1 - accumulator / PI2);
output *= amplitude;
buffer[i] = (synthesizeReplaceEnabled ? output : buffer[i] + output);
IncrementPhase();
}
}
private void SynthesizeSampleAndHold(int start, int end)
{
float[] buffer = GetBuffer();
float output;
for(int i = start; i < end; i++)
{
output = shOutput;
output *= amplitude;
buffer[i] = (synthesizeReplaceEnabled ? output : buffer[i] + output);
if(IncrementPhase())
{
shOutput = 1 - 2 * (float)rand.NextDouble();
}
}
}
private void HandleSampleRateChanged(object sender, EventArgs e)
{
CalculateIncrement();
}
#endregion
#region Properties
public override int Ordinal
{
get
{
return 1;
}
}
public override bool SynthesizeReplaceEnabled
{
get
{
return synthesizeReplaceEnabled;
}
set
{
synthesizeReplaceEnabled = value;
}
}
#endregion
#endregion
#region IProgrammable Members
public string GetParameterName(int index)
{
#region Require
if(index < 0 || index >= ParameterCount)
{
throw new ArgumentOutOfRangeException("index");
}
#endregion
string result = string.Empty;
string name = Name;
if(!string.IsNullOrEmpty(name))
{
name = name + " ";
}
switch((ParameterId)index)
{
case ParameterId.Delay:
result = name + "Delay";
break;
case ParameterId.Shape:
result = name + "Shape";
break;
case ParameterId.Speed:
result = name + "Speed";
break;
case ParameterId.ModulationWheelEnabled:
result = name + "Modulation Wheel Enabled";
break;
default:
Debug.Fail("Unhandled parameter");
break;
}
return result;
}
public string GetParameterLabel(int index)
{
#region Require
if(index < 0 || index >= ParameterCount)
{
throw new ArgumentOutOfRangeException("index");
}
#endregion
string result = string.Empty;
switch((ParameterId)index)
{
case ParameterId.Delay:
result = "Seconds";
break;
case ParameterId.Shape:
result = "Shape";
break;
case ParameterId.Speed:
result = "Hz";
break;
case ParameterId.ModulationWheelEnabled:
result = "On/Off";
break;
default:
Debug.Fail("Unhandled parameter");
break;
}
return result;
}
public string GetParameterDisplay(int index)
{
#region Require
if(index < 0 || index >= ParameterCount)
{
throw new ArgumentOutOfRangeException("index");
}
#endregion
string result = string.Empty;
switch((ParameterId)index)
{
case ParameterId.Delay:
{
float seconds = (float)delay / SamplesPerSecond;
result = seconds.ToString(Synthesizer.FormatInfo);
}
break;
case ParameterId.Shape:
result = shape.ToString();
break;
case ParameterId.Speed:
result = speed.ToString(Synthesizer.FormatInfo);
break;
case ParameterId.ModulationWheelEnabled:
result = (modulationWheelEnabled ? "On" : "Off");
break;
default:
Debug.Fail("Unhandled parameter");
break;
}
return result;
}
public float GetParameterValue(int index)
{
#region Require
if(index < 0 || index >= ParameterCount)
{
throw new ArgumentOutOfRangeException("index");
}
#endregion
float result = 0;
switch((ParameterId)index)
{
case ParameterId.Delay:
result = (float)delay / DelayScaler / SamplesPerSecond;
break;
case ParameterId.Shape:
result = (int)shape / (float)(ShapeCount - 1);
break;
case ParameterId.Speed:
result = speed / SpeedScaler;
break;
case ParameterId.ModulationWheelEnabled:
result = (modulationWheelEnabled ? 1 : 0);
break;
default:
Debug.Fail("Unhandled parameter");
break;
}
return result;
}
public void SetParameterValue(int index, float value)
{
#region Require
if(index < 0 || index >= ParameterCount)
{
throw new ArgumentOutOfRangeException("index");
}
#endregion
switch((ParameterId)index)
{
case ParameterId.Delay:
delay = (int)(value * DelayScaler * SamplesPerSecond);
break;
case ParameterId.Shape:
shape = (Shape)(int)Math.Round(value * (ShapeCount - 1));
break;
case ParameterId.Speed:
speed = value * SpeedScaler;
CalculateIncrement();
break;
case ParameterId.ModulationWheelEnabled:
modulationWheelEnabled = (value < 0.5 ? false : true);
if(modulationWheelEnabled)
{
amplitude = 0;
}
else
{
amplitude = 1;
}
break;
default:
Debug.Fail("Unhandled parameter");
break;
}
}
public int ParameterCount
{
get
{
return (int)ParameterId.ModulationWheelEnabled + 1;
}
}
#endregion
#region IControllable Members
public void ProcessControllerMessage(ControllerType type, float value)
{
if(type == ControllerType.ModulationWheel)
{
if(modulationWheelEnabled)
{
amplitude = value;
}
}
}
#endregion
}
}