Created
May 29, 2010 11:04
-
-
Save atsushieno/418212 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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