unit loro_sc;

interface
//
// C++ version (c) 2004 Russell Borogove / www.tinygod.com
// Delphi Pascal Version ©2005, Thaddy de Koning / www.thaddy.com
//
//	Lorenz/Rossler iterative function systems as LFOs
//

//
// This module defines the classes TLorenzOsc and TRosslerOsc - low frequency
// oscillators suitable for modeling 'analog drift' or other random-but-smooth
// processes. Both classes have identical APIs - you could unify the interface
// with virtual functions easily.
//
// SetSampleRate:
// Sets the sample rate at which the Iterate function will be called. Only
// meaningful in conjunction with the SetFreq function.
//
// SetFreq:
// Sets the fundamental frequency of the oscillator. The Rossler oscillator
// should exhibit harmonic peaks at multiples of that frequency; the Lorenz
// oscillator has a linear frequency-amplitude relation, so SetFreq will
// only control the scale of waveform features in a general way.
//
// Iterate:
// Advances the clock by one sample period and returns the value of the
// function at the current clock; it should be called once per sample-tick.
//
// GetCurrent:
// Returns the same value returned by the latest call to Iterate. Useful
// in cases where one generator modulates multiple destinations, for example.
//
// GetAlternate:
// Returns a value separate from the current value but correlated with it;
// these are the X and Y values used for the well-known "butterfly" plots
// of the Lorenz and Rossler functions. You can use GetAlternate if you
// want two separate LFOs which are related in mysterious ways at a low
// cost - for example, you can fine-tune one audio oscillator with the return
// from Iterate and another oscillator with the return from GetAlternate.
//
// Both the primary and alternate returns are calibrated to a -1.0 to +1.0
// range in normal usage. The implementation is discrete, though, so if the
// sample rate is low or the frequency high, it may occasionally jump outside
// that range -- the user is responsible for clamping if the range is
// critical.
//


//
// Lorenz function - very broad spectrum noise function with amplitude
// decreasing with increasing frequency, but tight short-term correlation.
//
// The scale of waveform features will change somewhat with the set frequency
// and sample rate, but not drastically - it's fairly fractal. In particular,
// there will not be substantial spectral peaks at multiples of the frequency
// selected by SetFreq.
//
uses
    {$IFDEF KOL} Kol{$ELSE} SysUtils{$ENDIF};
const
  LORENZ_SCALE:Single = 0.05107;
  LORENZ_ALT_SCALE:Single = 0.03679;

type
{$IFDEF KOL}
PLorenzOsc = ^TLorenzOsc;
TLorenzOsc = object(Tobj)
{$ELSE}
TLorenzOsc = class
{$ENDIF}
private
  mDX:Single;
  mDY:Single;
  mDZ:Single;
  mDT:Single;
  mFreq:Single;
  mX:Single;
  mY:Single;
  mZ:Single;
  mA:Single;
  mB:Single;
  mC:Single;
  mRate:Single;
public
{$IFDEF KOL}
  procedure init;virtual;
{$ELSE}
  constructor create;virtual;
{$ENDIF}
  procedure SetSampleRate(rate:Single );
  procedure SetFreq(freq:Single );
  function GetCurrent:Single;
  function GetAlternate:Single;
  function Iterate:Single;
end;


//
// Rossler function - broad spectrum noise function with amplitude
// decreasing with increasing frequency, and distinct harmonic peaks. The
// peaks should occur at harmonics of the frequency set by SetFreq.
//

const
ROSSLER_SCALE:Single = 0.05757;
ROSSLER_ALT_SCALE:Single = 0.06028;

type
{$IFDEF KOL}
PRosslerOsc = ^TRosslerOsc;
TRosslerOsc = object(Tobj)
{$ELSE}
TRosslerOsc = class
{$ENDIF}
private
  mDX:Single;
  mDY:Single;
  mDZ:Single;
  mDT:Single;
  mFreq:Single;
  mX:Single;
  mY:Single;
  mZ:Single;
  mA:Single;
  mB:Single;
  mC:Single;
  mRate:Single;
public
{$IFDEF KOL}
  procedure init;virtual;
{$ELSE}
  constructor create;virtual;
{$ENDIF}
  procedure SetSampleRate(rate:Single );
  procedure SetFreq(freq:Single);
  function GetCurrent:Single;
  function GetAlternate:Single;
  function Iterate:Single;
end;

{$IFDEF KOL}
function NewLorenzOsc:PLorenzOsc;
function NewRosslerOsc:PRosslerOsc;
{$ENDIF}
implementation
{$IFDEF KOL}
function NewLorenzOsc:PLorenzOsc;
begin
  New(Result,Create);
end;

function NewRosslerOsc:PRosslerOsc;
begin
  New(Result,Create);
end;
{$ENDIF}

{$IFDEF KOL}
procedure TLorenzOsc.init;
{$ELSE}
constructor Create;virtual;
{$ENDIF}
begin
  inherited;
  mA := 10.0;
  mB := 28.0;
  mC := 2.666;
  mDX := 0;
  mDY := 0;
  mDZ := 0;
  mX := 1;
  mY := 1;
  mZ := 1;
  mFreq := 440;
  SetSampleRate(44100);
  SetFreq(440);
end;

procedure TLorenzOsc.SetSampleRate(rate:Single );
begin
  mRate := rate;
  mDT := mFreq / rate;
end;

procedure TLorenzOsc.SetFreq(freq:Single );
begin
  mFreq := freq;
  mDT := freq / mRate;
end;


function TLorenzOsc.GetCurrent:Single;
begin
  Result:= mX * LORENZ_SCALE;
end;

function TLorenzOsc.GetAlternate:Single;
begin
  Result:= mY * LORENZ_ALT_SCALE;
end;

function TLorenzOsc.Iterate:Single;
begin
  mDX := mA * (mY-mX);
  mDY := mX * (mB-mZ) - mY;
  mDZ := mX * mY - mC * mZ;

  mX := mX + mDX * mDT;
  mY := mY + mDY * mDT;
  mZ := mZ + mDZ * mDT;

  Result:= mX * LORENZ_SCALE;
end;

{$IFDEF KOL}
procedure TRosslerOsc.init;
{$ELSE}
constructor TRosslerOsc.create;virtual;
{$ENDIF}
begin
  inherited;
  mA := 0.15;
  mB := 0.20;
  mC := 10;
  mDX := 0;
  mDY := 0;
  mDZ := 0;
  mX := 1;
  mY := 1;
  mZ := 1;
  mFreq := 440;
  SetSampleRate( 44100);
  SetFreq( 440);
end;

procedure TRosslerOsc.SetSampleRate(rate:Single );
begin
  mRate := rate;
  mDT := 2.91 * mFreq / rate;
end;

procedure TRosslerOsc.SetFreq(freq:Single);
begin
  mFreq := freq;
  mDT := 2.91 * freq / mRate;
end;


function TRosslerOsc.GetCurrent:Single;
begin
  Result:= mX * ROSSLER_SCALE;
end;

function TRosslerOsc.GetAlternate:Single;
begin
  Result:= mY * ROSSLER_ALT_SCALE;
end;

function TRosslerOsc.Iterate:Single;
begin
  mDX := -mY - mZ;
  mDY := mX + mA * mY;
  mDZ := mB + mZ * (mX - mC);

  mX := mX + mDX * mDT;
  mY := mY + mDY * mDT;
  mZ := mZ + mDZ * mDT;

  Result := mX * ROSSLER_SCALE;
end;

end.

