Skip to content

Instantly share code, notes, and snippets.

@atsushieno
Created May 29, 2010 11:04
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save atsushieno/418212 to your computer and use it in GitHub Desktop.
Save atsushieno/418212 to your computer and use it in GitHub Desktop.
// C# port of synth core part (mobilesynth/Classes/synth) from http://code.google.com/p/mobilesynth/
// It makes some sounds but am not sure if it works as expected.
using System;
namespace MobileSynth
{
public static class Constants
{
public const int DefaultSampleRate = 44100;
public const float OctaveCents = 1200;
public const int NotesPerOctave = 12;
public const float SampleRate = 44100.0f;
// Sample frequency
internal const float FS = 44100.0f;
// We can't push more notes on the stack than this
public const int KeyStackMaxSize = 64;
public const int MiddleAKey = 49;
public const float NotesPerOctaveF = 12.0f;
public const float MiddleAFrequency = 440.0f;
}
public static class Util
{
static readonly Random random = new Random ();
public static int Random ()
{
return random.Next ();
}
public static float KeyToFrequency (int key)
{
return Constants.MiddleAFrequency * (float)Math.Pow (2, (key - Constants.MiddleAKey) / Constants.NotesPerOctaveF);
}
}
#region Oscillator
public enum WaveType
{
Sine,
Square,
Triangle,
SawTooth,
ReverseSawTooth
}
public class Oscillator : Parameter
{
public Oscillator ()
{
wave_type_ = WaveType.Sine;
frequency_ = null;
sample_rate_ = Constants.DefaultSampleRate;
sample_num_ = 0;
}
WaveType wave_type_;
Parameter frequency_;
long sample_rate_;
long sample_num_;
public WaveType WaveType {
set { wave_type_ = value; }
}
public Parameter Frequency {
set { frequency_ = value; }
}
public int SampleRate {
set { sample_rate_ = value; }
}
public override float GetValue ()
{
if (frequency_ == null) {
return 0.0f;
}
float freq = frequency_.GetValue ();
if (freq < 0.01f) {
return 0.0f;
}
long period_samples = (long)(sample_rate_ / freq);
if (period_samples == 0) {
return 0.0f;
}
float x = (sample_num_ / (float)period_samples);
float value = 0;
switch (wave_type_) {
case WaveType.Sine:
value = (float)Math.Sin (2.0f * Math.PI * x);
break;
case WaveType.Square:
if (sample_num_ < (period_samples / 2)) {
value = 1.0f;
} else {
value = -1.0f;
}
break;
case WaveType.Triangle:
value = (float)(2.0f * Math.Abs (2.0f * x - 2.0f * Math.Floor (x) - 1.0f) - 1.0f);
break;
case WaveType.SawTooth:
value = (float)(2.0f * (x - Math.Floor (x) - 0.5f));
break;
case WaveType.ReverseSawTooth:
value = (float)(2.0f * (Math.Floor (x) - x + 0.5f));
break;
default:
throw new Exception ("unexpected enum value");
}
sample_num_ = (sample_num_ + 1) % (long)period_samples;
return value;
}
public void Reset ()
{
sample_num_ = 0;
}
public bool IsStart ()
{
return (sample_num_ == 0);
}
}
public class KeyboardOscillator : Parameter
{
public KeyboardOscillator (Oscillator osc1, Oscillator osc2, Parameter frequency)
{
base_frequency_ = frequency;
osc1_octave_ = 1;
osc2_octave_ = 1;
osc1_level_ = 0;
osc2_level_ = 0;
osc2_shift_ = 1.0f;
osc1_freq_ = new MutableParameter (0);
osc2_freq_ = new MutableParameter (0);
frequency_modulation_ = null;
sync_ = false;
osc1_ = osc1;
osc2_ = osc2;
osc1_.Frequency = osc1_freq_;
osc2_.Frequency = osc2_freq_;
}
Parameter base_frequency_;
float osc1_octave_;
float osc2_octave_;
float osc1_level_;
float osc2_level_;
float osc2_shift_;
MutableParameter osc1_freq_;
MutableParameter osc2_freq_;
Parameter frequency_modulation_;
bool sync_;
Oscillator osc1_;
Oscillator osc2_;
// Multiple to the specified octave
public float Osc1Octave {
set { osc1_octave_ = value; }
}
public float Osc2Octave {
set { osc2_octave_ = value; }
}
public float Osc1Level {
set { osc1_level_ = value; }
}
public float Osc2Level {
set { osc2_level_ = value; }
}
// Number of cents to shift osc2
public int Osc2Shift {
set {
if (value == 0) {
osc2_shift_ = 1.0f;
} else {
osc2_shift_ = (float)Math.Pow (2.0f, value / Constants.OctaveCents);
}
}
}
// Sync osc2 to osc1 (master)
public bool OscSync {
set { sync_ = value; }
}
// Can be NUL to disable frequency modulation, otherwise a multiplier of the
// current frequency intended to change over time.
public Parameter FrequencyModulation {
set { frequency_modulation_ = value; }
}
// Return the value of the combine oscillators
public override float GetValue ()
{
// osc2 is a slave to osc1 when sync is enabled.
if (sync_ && osc1_.IsStart ()) {
osc2_.Reset ();
}
float root_note = base_frequency_.GetValue ();
if (frequency_modulation_ != null) {
root_note *= frequency_modulation_.GetValue ();
}
osc1_freq_.SetValue ((float)(root_note * osc1_octave_));
var osc2_freq = root_note * osc2_octave_;
osc2_freq *= osc2_shift_;
osc2_freq_.SetValue ((float)osc2_freq);
float value = osc1_level_ * osc1_.GetValue () + osc2_level_ * osc2_.GetValue ();
// Clip
value = Math.Min (value, 1.0f);
return Math.Max (value, -1.0f);
}
}
#endregion
#region Parameter
public abstract class Parameter
{
// Return the current value of the parameter. Parameters that change over
// time expect to be invoked once for every sample. All modules assume they
// are using the same default sample rate.
public abstract float GetValue ();
protected Parameter ()
{
}
}
// A parameter that always returns a fixed value. This is mostly used for
// testing other components with a simple parameter.
public class FixedParameter : Parameter
{
public FixedParameter (float value)
{
value_ = value;
}
public override float GetValue ()
{
return value_;
}
float value_;
}
// A parameter that can be changed.
public class MutableParameter : Parameter
{
public MutableParameter (float value)
{
}
public override float GetValue ()
{
return value_;
}
public void SetValue (float value)
{
value_ = value;
}
float value_;
}
#endregion
#region Envelope
public class Envelope : Parameter
{
public enum State
{
Attack = 0,
Decay = 1,
Sustain = 2,
Release = 3,
Done = 4
}
public Envelope ()
{
attack_ = 0;
attack_slope_ = 0.0f;
decay_ = 0;
decay_end_ = 0;
decay_slope_ = 0.0f;
sustain_ = 1.0f;
release_ = 0;
release_start_ = 0;
release_end_ = 0;
release_slope_ = 0.0f;
min_ = 0.0f;
max_ = 1.0f;
current_ = 0;
state_ = State.Done;
}
long attack_;
float attack_slope_;
long decay_;
long decay_end_;
float decay_slope_;
float sustain_;
long release_;
long release_start_;
long release_end_;
float release_slope_;
float min_;
float max_;
long current_;
// sample
float last_value_;
State state_;
float release_start_value_;
// samples
public int Attack {
set {
if (!(value >= 0))
throw new ArgumentOutOfRangeException ("value");
attack_ = value;
}
}
// samples
public int Decay {
set {
if (!(value >= 0))
throw new ArgumentOutOfRangeException ("value");
decay_ = value;
}
}
// Sustain Volumne
public float Sustain {
set {
if (!(value >= 0))
throw new ArgumentOutOfRangeException ("value");
sustain_ = value;
}
}
// samples
public int Release {
set {
if (!(value >= 0))
throw new ArgumentOutOfRangeException ("value");
release_ = value;
}
}
// The value reached at the peak of the attack (Typically 1.0).
public float Max {
set {
if (!(value >= 0))
throw new ArgumentOutOfRangeException ("value");
max_ = value;
}
}
// The value at the start and release (Typically 0.0).
public float Min {
set {
if (!(value >= 0))
throw new ArgumentOutOfRangeException ("value");
min_ = value;
}
}
// Invoked when the note is pressed, resets all counters.
public void NoteOn ()
{
current_ = 0;
decay_end_ = attack_ + decay_;
if (attack_ == 0) {
attack_slope_ = 1;
} else {
attack_slope_ = (max_ - min_) / attack_;
}
if (decay_ == 0) {
decay_slope_ = 1;
} else {
decay_slope_ = (max_ - sustain_) / decay_;
}
state_ = State.Attack;
}
// Invoked when the note is released.
public void NoteOff ()
{
state_ = State.Release;
release_start_value_ = last_value_;
if (release_ == 0) {
release_slope_ = 1;
} else {
release_slope_ = (release_start_value_ - min_) / release_;
}
release_start_ = current_;
release_end_ = current_ + release_;
}
// Advances the clock and returns the value for the current step. Should not
// be called when Done() returns false.
public override float GetValue ()
{
current_++;
float value = 0;
// Check that we haven't transitioned longo the next state
if (state_ == State.Attack || state_ == State.Decay) {
if (current_ > decay_end_) {
state_ = State.Sustain;
} else if (current_ > attack_) {
state_ = State.Decay;
}
}
if (state_ == State.Sustain) {
if (sustain_ <= 0.0) {
state_ = State.Done;
}
}
if (state_ == State.Release) {
if (current_ > release_end_) {
state_ = State.Done;
}
}
switch (state_) {
case State.Attack:
value = current_ * attack_slope_ + min_;
value = (value < max_) ? value : max_;
break;
case State.Decay:
value = max_ - (current_ - attack_) * decay_slope_;
value = (value > sustain_) ? value : sustain_;
break;
case State.Sustain:
value = sustain_;
if (!(value > 0.0))
throw new Exception ();
break;
case State.Release:
value = release_start_value_ - (current_ - release_start_) * release_slope_;
value = (value > min_) ? value : min_;
break;
case State.Done:
value = min_;
break;
default:
throw new Exception (String.Format ("Unhandled state: {0}", state_));
}
last_value_ = value;
return value;
}
// True when the note has finished playing.
public bool IsReleased ()
{
return (state_ == State.Done);
}
}
#endregion
#region Modulation
public class LFO : Parameter
{
public LFO ()
{
}
Parameter level_;
Parameter oscillator_;
// Set the amount of modulation from [0, 1].
public Parameter Level {
set { level_ = value; }
}
// The specified oscillator should have its level set to 1
public Parameter Oscillator {
set { oscillator_ = value; }
}
// Returns an amplitude multiplier from [0, 1].
public override float GetValue ()
{
if (level_ == null || oscillator_ == null) {
return 1.0f;
}
float level = level_.GetValue ();
float m = (float)(0.5 * level);
float b = (float)(1.0 - m);
float value = (float)(m * oscillator_.GetValue () + b);
if (!(value >= 0))
throw new InvalidOperationException ("value >= 0");
if (!(value <= 1.0))
throw new InvalidOperationException ("value <= 1.0");
return value;
}
}
#endregion
#region LagProcessor
public class LagProcessor : Parameter
{
public LagProcessor (Parameter param)
{
samples_up_ = 0;
samples_down_ = 0;
param_ = param;
has_last_value_ = false;
last_value_ = 0;
envelope_.Attack = 0;
envelope_.Decay = 0;
envelope_.Sustain = 0;
envelope_.Release = 0;
}
long samples_up_;
long samples_down_;
Parameter param_;
bool has_last_value_;
float last_value_;
//long samples_;
Envelope envelope_ = new Envelope ();
public Parameter Param {
set { param_ = value; }
}
// Number of samples for each 1.0 change in the parameters value
public long Samples {
set {
SamplesUp = value;
SamplesDown = value;
}
}
public long SamplesUp {
set { samples_up_ = value; }
}
public long SamplesDown {
set { samples_down_ = value; }
}
// Reset the last value used (effectively disables glide from the last value)
public void Reset ()
{
has_last_value_ = false;
}
public override float GetValue ()
{
float value = param_.GetValue ();
if (!has_last_value_ || last_value_ != value) {
float diff = (float)Math.Abs (last_value_ - value);
if (!has_last_value_) {
// No previous value, so the envelope simply always returns the current
// value (in the sustain state)
envelope_.Min = 0;
envelope_.Max = value;
envelope_.Attack = 0;
envelope_.Decay = 0;
envelope_.Sustain = value;
envelope_.Release = 0;
envelope_.NoteOn ();
} else if (last_value_ < value) {
// Slope up
envelope_.Min = last_value_;
envelope_.Max = value;
envelope_.Attack = (int)(samples_up_ * diff);
envelope_.Decay = 0;
envelope_.Sustain = value;
envelope_.Release = 0;
envelope_.NoteOn ();
} else {
// Slope down
envelope_.Max = last_value_;
envelope_.Min = value;
envelope_.Attack = 0;
envelope_.Decay = 0;
envelope_.Sustain = last_value_;
envelope_.Release = (int)(samples_down_ * diff);
envelope_.NoteOn ();
envelope_.NoteOff ();
}
last_value_ = value;
has_last_value_ = true;
}
return envelope_.GetValue ();
}
}
#endregion
#region Arpeggio
public class Arpeggio : Parameter
{
// Describes how to determine the next note
public enum ArpeggioStep
{
Up,
Down,
UpDown,
Random
}
public Arpeggio (KeyStack keys)
{
keys_ = keys;
sample_ = 0;
samples_per_note_ = 1;
octaves_ = 1;
note_ = -1;
moving_up_ = true;
step_ = ArpeggioStep.Up;
}
KeyStack keys_;
long sample_;
long samples_per_note_;
int octaves_;
int note_;
bool moving_up_;
// Only used by UP_DOWN
ArpeggioStep step_;
public override float GetValue ()
{
int note = GetNote ();
if (note > 0) {
return Util.KeyToFrequency (GetNote ());
}
return 0.0f;
}
public int GetNote ()
{
int size = keys_.Size ();
if (size <= 0) {
return 0;
}
int max = octaves_ * size;
if (sample_ == 0) {
if (step_ == ArpeggioStep.Up) {
note_ = (note_ + 1) % max;
} else if (step_ == ArpeggioStep.Down) {
note_--;
if (note_ < 0) {
note_ = max - 1;
}
} else if (step_ == ArpeggioStep.UpDown) {
if (moving_up_) {
note_++;
if (note_ >= max - 1) {
note_ = max - 1;
moving_up_ = false;
}
} else {
note_--;
if (note_ <= 0) {
moving_up_ = true;
note_ = 0;
}
}
} else {
note_ = Util.Random () % max;
}
}
sample_ = (sample_ + 1) % samples_per_note_;
int octave = note_ / size;
return octave * Constants.NotesPerOctave + keys_.GetNote (note_ % size);
}
// The number of octaves up to include
public int Octaves {
set {
if (!(value >= 1))
throw new ArgumentOutOfRangeException ("value");
octaves_ = value;
}
}
public ArpeggioStep Step {
set { step_ = value; }
}
public int SamplesPerNote {
set { samples_per_note_ = value; }
}
public void Reset ()
{
sample_ = 0;
}
}
#endregion
#region Filter
// Interface for a filter based on some cutoff frequency (usually high or low
// pass). The input value is a sample and the output value is a new sample
// at that time.
public abstract class Filter
{
protected Filter ()
{
}
// The cutoff frequency of the filter.
public abstract Parameter Cutoff { set; }
public abstract float GetValue (float x);
}
// A simple LowPassFilter FIR filter
public class LowPassFilter : Filter
{
public LowPassFilter ()
{
cutoff_ = null;
last_cutoff_ = 0;
x1_ = 0;
x2_ = 0;
y1_ = 0;
y2_ = 0;
}
Parameter cutoff_;
// Used to keep the last value of the frequency cutoff. If it changes, we
// need to re-initialize the filter co-efficients.
float last_cutoff_;
// for input value x[k] and output y[k]
float x1_;
// input value x[k-1]
float x2_;
// input value x[k-2]
float y1_;
// output value y[k-1]
float y2_;
// output value y[k-2]
// filter coefficients
float a0_;
float a1_;
float a2_;
float b1_;
float b2_;
//float b3_;
public override Parameter Cutoff {
set { cutoff_ = value; }
}
public override float GetValue (float x)
{
if (cutoff_ == null) {
return x;
}
// Re-initialize the filter co-efficients if they changed
float cutoff = cutoff_.GetValue ();
if (cutoff <= 0.0f) {
return x;
} else if (cutoff < 0.001f) {
// Filtering all frequencies
return 0.0f;
}
if (Math.Abs (cutoff - last_cutoff_) > 0.001f) {
Reset (cutoff);
last_cutoff_ = cutoff;
}
float y = a0_ * x + a1_ * x1_ + a2_ * x2_ + b1_ * y1_ + b2_ * y2_;
x1_ = x;
x2_ = x1_;
y2_ = y1_;
y1_ = y;
return y;
}
private void Reset (float frequency)
{
// Number of filter passes
float n = 1;
// 3dB cutoff frequency
float f0 = frequency;
// 3dB cutoff correction
float c = (float)Math.Pow (Math.Pow (2, 1.0f / n) - 1, -0.25);
// Polynomial coefficients
float g = 1;
float p = (float)Math.Sqrt (2);
// Corrected cutoff frequency
float fp = c * (f0 / Constants.FS);
// Warp cutoff freq from analog to digital domain
float w0 = (float)Math.Tan (Math.PI * fp);
// Calculate the filter co-efficients
float k1 = p * w0;
float k2 = g * w0 * w0;
a0_ = k2 / (1 + k1 + k2);
a1_ = 2 * a0_;
a2_ = a0_;
b1_ = 2 * a0_ * (1 / k2 - 1);
b2_ = 1 - (a0_ + a1_ + a2_ + b1_);
}
}
// Simple VCF
public class ResonantFilter : Filter
{
public ResonantFilter ()
{
cutoff_ = null;
resonance_ = 0.0f;
y1_ = 0.0f;
y2_ = 0.0f;
y3_ = 0.0f;
y4_ = 0.0f;
oldx_ = 0.0f;
oldy1_ = 0.0f;
oldy2_ = 0.0f;
oldy3_ = 0.0f;
}
Parameter cutoff_;
float resonance_;
float y1_;
float y2_;
float y3_;
float y4_;
float oldx_;
float oldy1_;
float oldy2_;
float oldy3_;
public override Parameter Cutoff {
set { cutoff_ = value; }
}
public float Resonance {
set { resonance_ = value; }
}
public override float GetValue (float x)
{
if (cutoff_ == null) {
return x;
}
float cutoff = cutoff_.GetValue ();
float f = 2.0f * cutoff / Constants.SampleRate;
float k = 3.6f * f - 1.6f * f * f - 1;
float p = (k + 1.0f) * 0.5f;
float scale = (float)Math.Pow (Math.E, (1.0f - p) * 1.386249);
float r = resonance_ * scale;
float outv = x - r * y4_;
y1_ = outv * p + oldx_ * p - k * y1_;
y2_ = y1_ * p + oldy1_ * p - k * y2_;
y3_ = y2_ * p + oldy2_ * p - k * y3_;
y4_ = y3_ * p + oldy3_ * p - k * y4_;
y4_ = y4_ - (float)Math.Pow (y4_, 3.0f) / 6.0f;
oldx_ = outv;
oldy1_ = y1_;
oldy2_ = y2_;
oldy3_ = y3_;
return outv;
}
}
// Encapsulates the logic for calculating the filter cutoff frequency
public class FilterCutoff : Parameter
{
public FilterCutoff ()
{
cutoff_ = -1.0f;
modulation_ = null;
}
// base
float cutoff_;
Envelope envelope_ = new Envelope ();
Parameter modulation_;
public override float GetValue ()
{
float value = cutoff_ * envelope_.GetValue ();
if (modulation_ != null) {
value *= modulation_.GetValue ();
}
return value;
}
public float Cutoff {
set { cutoff_ = value; }
}
public Parameter Modulation {
set { modulation_ = value; }
}
public Envelope Envelope {
get { return envelope_; }
}
}
#endregion
#region KeyStack
public class KeyStack
{
int size_;
int[] notes_;
// Number of times the note at the position was pressed
int[] count_;
public KeyStack ()
{
notes_ = new int[Constants.KeyStackMaxSize];
count_ = new int[Constants.KeyStackMaxSize];
}
// Returns true if this was the first note pushed on to the key stack
public bool NoteOn (int note)
{
if (!(size_ < Constants.KeyStackMaxSize))
throw new Exception ();
for (int i = 0; i < size_; ++i) {
if (notes_[i] == note) {
count_[i]++;
return false;
}
}
notes_[size_] = note;
count_[size_] = 1;
size_++;
return true;
}
// Returns true if this was the last note removed from the key stack
public bool NoteOff (int note)
{
for (int i = 0; i < size_; ++i) {
if (notes_[i] == note) {
count_[i]--;
if (count_[i] == 0) {
// Remove this element from the stack -- copy all elements above
for (int j = i; j < size_ - 1; ++j) {
notes_[j] = notes_[j + 1];
count_[j] = count_[j + 1];
}
size_--;
}
return true;
}
}
// The note wasn't on the stack. The multi-touch events on the iphone seem
// to be flaky, so we don't worry if we were asked to remove something that
// was not on the stack. The controller also calls our clear() method when
// no touch events are left as a fallback.
return false;
}
// Returns the current not, or 0 if no note is playing.
public int GetCurrentNote ()
{
if (size_ > 0) {
return notes_[size_ - 1];
}
return 0;
}
// Return the note at the specified position in the stack. num must be less
// than size.
public int GetNote (int num)
{
if (num >= size_) {
return 0;
}
return notes_[num];
}
public bool IsNoteInStack (int note)
{
for (int i = 0; i < size_; ++i) {
if (notes_[i] == note) {
return true;
}
}
return false;
}
public int Size ()
{
int count = 0;
for (int i = 0; i < size_; ++i) {
count += count_[i];
}
return count;
}
public void Clear ()
{
size_ = 0;
}
}
#endregion
#region Controller
public class Volume : Parameter
{
public Volume ()
{
level_ = 1.0f;
}
// base
float level_;
Envelope envelope_ = new Envelope ();
Parameter modulation_;
public override float GetValue ()
{
float value = level_ * envelope_.GetValue ();
if (modulation_ != null) {
value *= modulation_.GetValue ();
}
return value;
}
public float Level {
set { level_ = value; }
}
public Parameter Modulation {
set { modulation_ = value; }
}
public Envelope Envelope {
get { return envelope_; }
}
}
public enum ModulationSource
{
Square,
Triangle,
SawTooth,
ReverseSawTooth
}
// TODO(allen): How do you determine the sustain length?
// Is it sustain = period - (attack + decay + release)?
// LFO_FILTER_ENVELOPE,
// TODO(allen): OSC2 needs a manual frequency control for this to work, i think.
// OSC2,
public enum ModulationDestination
{
Wave,
// Tremelo
Pitch,
// Vibrato
Filter
// TODO(allen): Is this Ring modulation?
// LFO_DEST_OSC2,
}
// A shift in frequency by the specified amount. The frequency gets
// multiplied by 2^n
[Flags]
public enum OctaveShift
{
Octave1 = 1,
Octave2 = 2,
Octave4 = 4,
Octave8 = 8,
Octave16 = 16
}
public class Controller
{
public Controller ()
{
key_frequency_ = new MutableParameter (0.0f);
arpeggio_enabled_ = false;
arpeggio_ = new Arpeggio (key_stack_);
key_lag_processor_ = new LagProcessor (arpeggio_);
combined_osc_ = new KeyboardOscillator (osc1_, osc2_, key_lag_processor_);
osc_sync_ = false;
modulation_source_ = ModulationSource.Square;
modulation_destination_ = ModulationDestination.Wave;
modulation_frequency_ = new MutableParameter (0.0f);
modulation_amount_ = new MutableParameter (0.0f);
modulation_osc_.Frequency = modulation_frequency_;
modulation_.Oscillator = modulation_osc_;
modulation_.Level = modulation_amount_;
lowpass_filter_.Cutoff = filter_cutoff_;
resonant_filter_.Cutoff = filter_cutoff_;
ResetRouting ();
}
KeyStack key_stack_ = new KeyStack ();
MutableParameter key_frequency_;
bool arpeggio_enabled_;
Arpeggio arpeggio_;
LagProcessor key_lag_processor_;
Oscillator osc1_ = new Oscillator ();
Oscillator osc2_ = new Oscillator ();
// The two oscillators combined
KeyboardOscillator combined_osc_;
Volume volume_ = new Volume ();
bool osc_sync_;
ModulationSource modulation_source_;
ModulationDestination modulation_destination_;
MutableParameter modulation_frequency_;
Oscillator modulation_osc_ = new Oscillator ();
MutableParameter modulation_amount_;
LFO modulation_ = new LFO ();
FilterCutoff filter_cutoff_ = new FilterCutoff ();
LowPassFilter lowpass_filter_ = new LowPassFilter ();
ResonantFilter resonant_filter_ = new ResonantFilter ();
// Volume [0, 1.0]
public float Volume {
set { volume_.Level = value; }
}
// Start/Stop playing a note. These may trigger the Attack and Release of the
// volume and filter envelopes, depending on the order of the on/off events.
// It is an error to call NoteOff() for a note that was never the argument of
// NoteOn();
public void NoteOn (int note)
{
if (!(note >= 1))
throw new ArgumentOutOfRangeException ("note");
if (!(note <= 88))
throw new ArgumentOutOfRangeException ("note");
key_stack_.NoteOn (note);
if (key_stack_.Size () == 1) {
// This is the first note played, so start attacking
key_lag_processor_.Reset ();
arpeggio_.Reset ();
VolumeEnvelope.NoteOn ();
FilterEnvelope.NoteOn ();
}
float frequency = Util.KeyToFrequency (key_stack_.GetCurrentNote ());
key_frequency_.SetValue (frequency);
}
public void NoteOff ()
{
key_stack_.Clear ();
VolumeEnvelope.NoteOff ();
FilterEnvelope.NoteOff ();
}
public void NoteOnFrequency (float frequency)
{
key_frequency_.SetValue (frequency);
VolumeEnvelope.NoteOn ();
FilterEnvelope.NoteOn ();
}
// Invoked when all notes have been released as a fallback
public void NoteOff (int note)
{
key_stack_.NoteOff (note);
if (key_stack_.Size () == 0) {
// All notes were release, so start the release phase of the envelope
NoteOff ();
} else {
// There are still notes on key stack -- switch!
float frequency = Util.KeyToFrequency (key_stack_.GetCurrentNote ());
key_frequency_.SetValue (frequency);
}
}
// True when nothing is playing
public bool IsReleased ()
{
return (VolumeEnvelope.IsReleased () || FilterEnvelope.IsReleased ());
}
public float SampleRate {
set {
osc1_.SampleRate = (int)value;
osc2_.SampleRate = (int)value;
}
}
// OSC 1
public float Osc1Level {
set { combined_osc_.Osc1Level = value; }
}
public WaveType Osc1WaveType {
set { osc1_.WaveType = value; }
}
public OctaveShift Osc1Octave {
set { combined_osc_.Osc1Octave = (int)value; }
}
// OSC 2
public float Osc2Level {
set { combined_osc_.Osc2Level = value; }
}
public WaveType Osc2WaveType {
set { osc2_.WaveType = value; }
}
public OctaveShift Osc2Octave {
set { combined_osc_.Osc2Octave = (int)value; }
}
public int Ocs2Shift {
set { combined_osc_.Osc2Shift = value; }
}
public bool OscSync {
set { combined_osc_.OscSync = value; }
}
public Envelope VolumeEnvelope {
get { return volume_.Envelope; }
}
public Envelope FilterEnvelope {
get { return filter_cutoff_.Envelope; }
}
public ModulationSource ModulationSource {
set {
modulation_source_ = value;
ResetRouting ();
}
}
public ModulationDestination ModulationDestination {
set {
modulation_destination_ = value;
ResetRouting ();
}
}
public float ModulationAmount {
set { modulation_amount_.SetValue (value); }
}
public float ModulationFrequency {
set { modulation_frequency_.SetValue (value); }
}
public float FilterCutoff {
set { filter_cutoff_.Cutoff = value; }
}
// [0.0, 1.0]
public float FilterResonance {
set { resonant_filter_.Resonance = value; }
}
public long GlideSamples {
set { key_lag_processor_.Samples = value; }
}
public bool ArpeggioEnabled {
set {
arpeggio_enabled_ = value;
ResetRouting ();
}
}
public int ArpeggioSamples {
set { arpeggio_.SamplesPerNote = value; }
}
public int ArpeggioOctaves {
set { arpeggio_.Octaves = value; }
}
public Arpeggio.ArpeggioStep ArpeggioStep {
set { arpeggio_.Step = value; }
}
// Get a single sample
public float GetSample ()
{
if (VolumeEnvelope.IsReleased () || FilterEnvelope.IsReleased ()) {
return 0;
}
// Combined oscillators, volume/envelope/modulation
float value = combined_osc_.GetValue ();
// Clip!
value = Math.Max (-1.0f, value);
value = Math.Min (1.0f, value);
// Combined filter with envelope/modulation
value = lowpass_filter_.GetValue (value);
value = resonant_filter_.GetValue (value);
// Clip!
value = Math.Max (-1.0f, value);
value = Math.Min (1.0f, value);
// Adjust volume
value *= volume_.GetValue ();
return value;
}
public void GetFloatSamples (float[] buffer, int size)
{
for (int i = 0; i < size; ++i) {
buffer[i] = GetSample ();
}
}
public void GetIntSamples (int[] buffer, int size)
{
for (int i = 0; i < size; i++)
buffer[i] = (int) GetSample ();
}
// Invoked when one of the routing parameters changes, such as the source
// or destination of modulation.
private void ResetRouting ()
{
switch (modulation_source_) {
case ModulationSource.Square:
modulation_osc_.WaveType = WaveType.Square;
break;
case ModulationSource.Triangle:
modulation_osc_.WaveType = WaveType.Triangle;
break;
case ModulationSource.SawTooth:
modulation_osc_.WaveType = WaveType.SawTooth;
break;
case ModulationSource.ReverseSawTooth:
modulation_osc_.WaveType = WaveType.ReverseSawTooth;
break;
default:
throw new Exception ();
}
// Reset the destinations
volume_.Modulation = null;
filter_cutoff_.Modulation = null;
combined_osc_.FrequencyModulation = null;
// Route modulation into the correct pipeline
switch (modulation_destination_) {
case ModulationDestination.Wave:
// Modulate the volume (tremelo)
volume_.Modulation = modulation_;
break;
case ModulationDestination.Pitch:
// Modulate the frequency (vibrato)
combined_osc_.FrequencyModulation = modulation_;
break;
case ModulationDestination.Filter:
// Modulate the cutoff frequency
filter_cutoff_.Modulation = modulation_;
break;
default:
throw new Exception ();
}
if (arpeggio_enabled_) {
key_lag_processor_.Param = arpeggio_;
} else {
key_lag_processor_.Param = key_frequency_;
}
}
}
#endregion
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment