// maybe save as a pas file instead of txt to get some syntax coloring in your // favourite editor. // ///////////////////////////////////// // Wires // A signal is just a double precision float TSignal = Double; // Array of the four control points for a Bezier curve TKnobsBezierPoints = array[ 0 .. 3] of TPoint; // Drawing params for a wire TKnobsBezierParams = record Angle1 : Integer; Angle2 : Integer; Strength1 : Integer; Strength2 : Integer; end; function AddPoints( A, B: TPoint): TPoint; // Add points begin with Result do begin x := A.x + B.x; y := A.y + B.y; end; end; function SubPoints( A, B: TPoint): TPoint; // Subtract points begin with Result do begin x := A.x - B.x; y := A.y - B.y; end; end; function MultPoint( A: TPoint; M: Integer): TPoint; begin Result := Point( A.x * M, A.y * M); end; function DivPoint( A: TPoint; D: Integer): TPoint; begin Result := Point( A.x div D, A.y div D); end; function Rotate( A: TPoint; anAngle: TSignal): TPoint; // Rotate a point around a point (0,0) begin anAngle := DegToRad( anAngle); with Result do begin x := Round( Cos( anAngle) * A.x - Sin( anAngle) * A.y); y := Round( Sin( anAngle) * A.x + Cos( anAngle) * A.y); end; end; procedure CalcBezierCurve( var Points: TKnobsBezierPoints; const Params: TKnobsBezierParams); // Used to calculate wire bending var Dir1 : TPoint; Dir2 : TPoint; begin Dir1 := Rotate( SubPoints( Points[ 0], Points[ 3]), Params.Angle1); Dir2 := Rotate( SubPoints( Points[ 0], Points[ 3]), Params.Angle2); Points[ 1] := SubPoints( Points[ 0], DivPoint( MultPoint( Dir1, Params.Strength1), 100)); Points[ 2] := AddPoints( Points[ 3], DivPoint( MultPoint( Dir2, Params.Strength2), 100)); end; procedure TKnobsWire.Wiggle; // Set bend params for a wire with a little random variation begin with FBezierParams do begin Angle1 := 15 + Random( 21); Strength1 := 20 + Random( 21); Angle2 := 20 + Random( 21); Strength2 := 20 + Random( 21); end; end; procedure TKnobsWireOverlay.PaintWire( aDC: HDC; aWire: TKnobsWire); var Points : TKnobsBezierPoints; begin with aWire do begin if FSource.HasParent and FDestination.HasParent and ( WireThickness > 0) then begin with FSource do Points[ 0] := ClientToScreen( CenterPoint); // Get source connector center point Points[ 0] := ScreenToClient( Points[ 0]); // Into wire overlay coordinate system with FDestination // Idem for destination connector do Points[ 3] := ClientToScreen( CenterPoint); Points[ 3] := ScreenToClient( Points[ 3]); if CurvedLines then begin CalcBezierCurve( Points, aWire.BezierParams); // Calculate the curve // The curve is drawn from the first point to the fourth point, using the second and third points as control points. PolyBezier( aDC, Points, 4); // Paint it to the Device Context (canvas) end else begin with Points[ 0] do MoveToEx( aDC, X, Y, nil); with Points[ 3] do LineTo ( aDC, X, Y ); end; end; end; end; // //////////////////////////// // Envelopes function Normalize( const aValue: TSignal): TSignal; inline; begin if Abs( aValue) < 1e-20 then Result := 0 else Result := aValue; end; function ExpAttack( const aTime, aDuration, aStartValue, anEndValue: TSignal): TSignal; // [0,1] -> [0,1] var Unscaled : TSignal; aDur : TSignal; aTim : TSignal; begin aDur := Max( 1, aDuration); aTim := Clip( aTime, 0, aDur) / aDur; Unscaled := ( Power( 2, 10 * aTim) - 1) / 1023; Result := Normalize(( 1 - Unscaled) * aStartValue + Unscaled * anEndValue); end; function LinAttack( const aTime, aDuration, aStartValue, anEndValue: TSignal): TSignal; // [0,1] -> [0,1] var Unscaled : TSignal; aDur : TSignal; aTim : TSignal; begin aDur := Max( 1, aDuration); aTim := Clip( aTime, 0, aDur) / aDur; Unscaled := aTim; Result := Normalize(( 1 - Unscaled) * aStartValue + Unscaled * anEndValue); end; function LogAttack( const aTime, aDuration, aStartValue, anEndValue: TSignal): TSignal; // [0,1] -> [0,1] var Unscaled : TSignal; aDur : TSignal; aTim : TSignal; begin aDur := Max( 1, aDuration); aTim := Clip( aTime, 0, aDur) / aDur; Unscaled := ( 1024 - Power( 2, 10 - 10 * aTim)) / 1023; Result := Normalize(( 1 - Unscaled) * aStartValue + Unscaled * anEndValue); end; function ExpDecay( const aTime, aDuration, aStartValue, anEndValue: TSignal): TSignal; // [0,1] -> [0,1] var Unscaled : TSignal; aDur : TSignal; aTim : TSignal; begin aDur := Max( 1, aDuration); aTim := Clip( aTime, 0, aDur) / aDur; Unscaled := ( Power( 2, 10 - 10 * aTim) - 1) / 1023; Result := Normalize(( 1 - Unscaled) * anEndValue + Unscaled * aStartValue); end; function LinDecay( const aTime, aDuration, aStartValue, anEndValue: TSignal): TSignal; // [0,1] -> [0,1] var Unscaled : TSignal; aDur : TSignal; aTim : TSignal; begin aDur := Max( 1, aDuration); aTim := Clip( aTime, 0, aDur) / aDur; Unscaled := 1 - aTim; Result := Normalize(( 1 - Unscaled) * anEndValue + Unscaled * aStartValue); end; function LogDecay( const aTime, aDuration, aStartValue, anEndValue: TSignal): TSignal; // [0,1] -> [0,1] var Unscaled : TSignal; aDur : TSignal; aTim : TSignal; begin aDur := Max( 1, aDuration); aTim := Clip( aTime, 0, aDur) / aDur; Unscaled := ( 1024 - Power( 2, 10 * aTim)) / 1023; Result := Normalize(( 1 - Unscaled) * anEndValue + Unscaled * aStartValue); end; // And a calling example with static times and static level, filling up the // width and height of the viewer panel. MaxValue being the height of the // viewer, MinValue being zero; FYData being an array of Y values, the index // representing the X value. procedure TKnobsDataViewer.FillEnvAdsr; var i : Integer; Knee1 : Integer; Knee2 : Integer; Knee3 : Integer; Sust : TSignal; begin SetLength( FYData, Width); // Width values in array Knee1 := Width div 8; // attack phase 1/8 of width Knee2 := Width div 2; // sustain sets in at 1/2 width Knee3 := 2 * Width div 3; // release at 2/3 width Sust := MinValue + ( MaxValue - MinValue) / 3; // Sustain level is 1/3 of the height for i := 0 to Length( FYData) - 1 // Calc array values do begin if i < Knee1 // Attack phase then FYData[ i] := LinAttack( i, Knee1, MinValue, MaxValue) else if i < Knee2 // Decay phase then FYData[ i] := ExpDecay( i - Knee1, Knee3 - Knee2, MaxValue, Sust) else if i < Knee3 // Sustain phase then FYData[ i] := Sust else FYData[ i] := ExpDecay( i - Knee3, Width - Knee3, Sust, MinValue); // Release end; end;