unit u_main;

{  DISCLAIMER
This software is free to use, copy or distrubute as a whole or in parts, as you like.
I, the author do not guarantee proper operation, suitability for any use,
  correctness of results, etc.....
I do not take any responsibility for misbehaviour of the software,
  including affecting the system on which it runs.
I can give some support regarding the operation of the program.
I cannot give any support for problems related to Free Pascal or Lazarus.
If you have such, visit the FPC and Lazarus forum(s).
This software was developed and tested on
  - WXP and W7 with FPC version 2.6.2 and Lazarus version 1.0.14.
  - Linux Mint with FPC version 3.0.0 and Lazarus version 1.6.

September 2018 jan@breem.nl
}

{
About the size of the executable: FPC/Laz normally throws an enormous amount of
debug info into the binary. You can separate the debug info by setting:
> Project > Project Options > Linker > Use external gdb debug symbols file
or switch off debug info completely.
}

{$mode objfpc}{$H+}

interface

uses Classes, Forms, Controls, Dialogs, StdCtrls, ExtCtrls;

type
  TFrm_BassCalc = class(TForm)
    ButtReCalculate: TButton;
    ButtDefaults: TButton;
    ButtDefaultColors: TButton;
    ButtSaveAs: TButton;
    ButtOpen: TButton;
    CbxShowConeVelocity: TCheckBox;
    CbxUseCompensator: TCheckBox;
    CbxShowAmpV: TCheckBox;
    CbxShowComp: TCheckBox;
    CbxShowAll: TCheckBox;
    CbxShowLowUp: TCheckBox;
    CbxShowSpeakerX: TCheckBox;
    CbxShowSpl: TCheckBox;
    CbxShowAmpI: TCheckBox;
    CbxUseLowUp: TCheckBox;
    CbxUseZobel: TCheckBox;
    CbxShowZspkr: TCheckBox;
    CbxShowZspkrInBox: TCheckBox;
    CbxShowZzobel: TCheckBox;
    CbxShowZzobelSpkrBox: TCheckBox;
    ColorDialog: TColorDialog;
    Label2: TLabel;
    Label22: TLabel;
    Label3: TLabel;
    Label47: TLabel;
    Label48: TLabel;
    Label54: TLabel;
    Label55: TLabel;
    Label56: TLabel;
    Label57: TLabel;
    Label58: TLabel;
    Label69: TLabel;
    Label71: TLabel;
    Label72: TLabel;
    Label73: TLabel;
    Label76: TLabel;
    LblSelColor1: TLabel;
    LblSelColor2: TLabel;
    LblSelColor3: TLabel;
    LblSelColor4: TLabel;
    LblSelColor8: TLabel;
    LblSelColor5: TLabel;
    LblSelColor6: TLabel;
    LblSelColor7: TLabel;
    LblSelColor10: TLabel;
    LblSelColor9: TLabel;
    LblSelColor11: TLabel;
    LblSelColor12: TLabel;
    OpenDialog: TOpenDialog;
    RbRange2: TRadioButton;
    SaveDialog: TSaveDialog;
    GroupBox5: TGroupBox;
    GroupBox8: TGroupBox;
    GroupBox9: TGroupBox;
    Label59: TLabel;
    Label60: TLabel;
    Label61: TLabel;
    Label62: TLabel;
    Label63: TLabel;
    Label64: TLabel;
    Label65: TLabel;
    Label66: TLabel;
    Label67: TLabel;
    Label68: TLabel;
    Label70: TLabel;
    RbRange5: TRadioButton;
    RbRange6: TRadioButton;
    RbRange8: TRadioButton;
    TbxCtot: TEdit;
    TbxQcomp: TEdit;
    TbxQtc: TEdit;
    TbxInputLevel: TEdit;
    TbxRrbx: TEdit;
    TbxRbx: TEdit;
    TbxLctot: TEdit;
    TbxRzo2: TEdit;
    TbxCzo2: TEdit;
    GroupBox3: TGroupBox;
    GroupBox4: TGroupBox;
    GroupBox6: TGroupBox;
    Label28: TLabel;
    Label29: TLabel;
    Label30: TLabel;
    Label31: TLabel;
    Label32: TLabel;
    Label33: TLabel;
    Label34: TLabel;
    Label35: TLabel;
    Label36: TLabel;
    Label37: TLabel;
    Label38: TLabel;
    Label39: TLabel;
    Label40: TLabel;
    Label41: TLabel;
    Label42: TLabel;
    Label43: TLabel;
    Label44: TLabel;
    Label45: TLabel;
    Label46: TLabel;
    Label49: TLabel;
    Label50: TLabel;
    Label51: TLabel;
    Label52: TLabel;
    Label53: TLabel;
    RbRange1: TRadioButton;
    RbRange3: TRadioButton;
    RbRange4: TRadioButton;
    RbRange7: TRadioButton;
    TbxRgy2: TEdit;
    TbxCompR4: TEdit;
    TbxRgy1: TEdit;
    TbxCgy: TEdit;
    TbxLgy: TEdit;
    TbxRlu1: TEdit;
    TbxRlu2: TEdit;
    TbxRlu3: TEdit;
    TbxClu1: TEdit;
    TbxDipFreq: TEdit;
    TbxRzo1: TEdit;
    TbxLzo1: TEdit;
    TbxCzo1: TEdit;
    TbxRms: TEdit;
    TbxBl: TEdit;
    TbxRrms: TEdit;
    TbxRe: TEdit;
    TbxLe: TEdit;
    TbxMms: TEdit;
    TbxSd: TEdit;
    TbxCmms: TEdit;
    TbxCms: TEdit;
    TbxRco1: TEdit;
    TbxRco2: TEdit;
    TbxCco1: TEdit;
    TbxLcms: TEdit;
    TbxLcbx: TEdit;
    TbxVbx: TEdit;
    TbxCbx: TEdit;
    GroupBox1: TGroupBox;
    GroupBox2: TGroupBox;
    Label1: TLabel;
    Label10: TLabel;
    Label11: TLabel;
    Label12: TLabel;
    Label13: TLabel;
    Label14: TLabel;
    Label15: TLabel;
    Label16: TLabel;
    Label17: TLabel;
    Label18: TLabel;
    Label19: TLabel;
    Label20: TLabel;
    Label21: TLabel;
    Label23: TLabel;
    Label24: TLabel;
    Label25: TLabel;
    Label26: TLabel;
    Label27: TLabel;
    Label8: TLabel;
    Label9: TLabel;
    Timer1: TTimer;
    procedure ButtDefaultsClick(Sender: TObject);
    procedure ButtReCalculateClick(Sender: TObject);
    procedure ButtDefaultColorsClick(Sender: TObject);
    procedure ButtOpenClick(Sender: TObject);
    procedure ButtSaveAsClick(Sender: TObject);
    procedure CbxShowAmpIChange(Sender: TObject);
    procedure CbxShowAmpVChange(Sender: TObject);
    procedure CbxShowCompChange(Sender: TObject);
    procedure CbxShowConeVelocityChange(Sender: TObject);
    procedure CbxShowHighUpChange(Sender: TObject);
    procedure CbxShowLowUpChange(Sender: TObject);
    procedure CbxShowAllChange(Sender: TObject);
    procedure CbxShowSpeakerXChange(Sender: TObject);
    procedure CbxShowSplChange(Sender: TObject);
    procedure CbxShowZspkrChange(Sender: TObject);
    procedure CbxShowZspkrInBoxChange(Sender: TObject);
    procedure CbxShowZzobelChange(Sender: TObject);
    procedure CbxShowZzobelSpkrBoxChange(Sender: TObject);
    procedure CbxUseCompensatorChange(Sender: TObject);
    procedure CbxUseLowUpChange(Sender: TObject);
    procedure CbxUseZobelChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
    procedure LblSelColor9Click(Sender: TObject);
    procedure LblSelColor11Click(Sender: TObject);
    procedure LblSelColor12Click(Sender: TObject);
    procedure LblSelColor1Click(Sender: TObject);
    procedure LblSelColor2Click(Sender: TObject);
    procedure LblSelColor3Click(Sender: TObject);
    procedure LblSelColor4Click(Sender: TObject);
    procedure LblSelColor8Click(Sender: TObject);
    procedure LblSelColor5Click(Sender: TObject);
    procedure LblSelColor6Click(Sender: TObject);
    procedure LblSelColor7Click(Sender: TObject);
    procedure LblSelColor10Click(Sender: TObject);
    procedure RbRange1Change(Sender: TObject);
    procedure RbRange2Change(Sender: TObject);
    procedure RbRange3Change(Sender: TObject);
    procedure RbRange4Change(Sender: TObject);
    procedure RbRange5Change(Sender: TObject);
    procedure RbRange6Change(Sender: TObject);
    procedure RbRange7Change(Sender: TObject);
    procedure RbRange8Change(Sender: TObject);
    procedure TbxBlChange(Sender: TObject);
    procedure TbxCco1Change(Sender: TObject);
    procedure TbxCgyChange(Sender: TObject);
    procedure TbxClu1Change(Sender: TObject);
    procedure TbxCmsChange(Sender: TObject);
    procedure TbxInputLevelChange(Sender: TObject);
    procedure TbxLeChange(Sender: TObject);
    procedure TbxMmsChange(Sender: TObject);
    procedure TbxRbxChange(Sender: TObject);
    procedure TbxRco1Change(Sender: TObject);
    procedure TbxRco2Change(Sender: TObject);
    procedure TbxReChange(Sender: TObject);
    procedure TbxRgy1Change(Sender: TObject);
    procedure TbxRgy2Change(Sender: TObject);
    procedure TbxRlu1Change(Sender: TObject);
    procedure TbxRlu2Change(Sender: TObject);
    procedure TbxRlu3Change(Sender: TObject);
    procedure TbxRzo1Change(Sender: TObject);
		procedure TbxLzo1Change(Sender: TObject);
 		procedure TbxCzo1Change(Sender: TObject);
		procedure TbxRzo2Change(Sender: TObject);
		procedure TbxCzo2Change(Sender: TObject);
    procedure TbxRmsChange(Sender: TObject);
    procedure TbxSdChange(Sender: TObject);
    procedure TbxVbxChange(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);

  private
    { private declarations }
  public
    { public declarations }
  end;

var
  Frm_BassCalc: TFrm_BassCalc;

implementation

uses Graphics, StrUtils, SysUtils, ucomplex, u_LogGraph, u_complex;

// General
const VersionString = 'BassCalc V 1.0';
const nFrequencies = 1000;

var
  SystemReady: boolean; // prevents plotting when plotform is not yet ready
  ParameterFileName: string;
  Slash: string;

type
  tTrace = array[0..nFrequencies] of real;

var
// Traces Z
  TraceSpeakerZMag, TraceSpeakerZPhase,           // Spkr in air
  TraceSpeakerBoxZMag, TraceSpeakerBoxZPhase,     // Spkr in box
  TraceZobelZMag, TraceZobelZPhase,               // Zobel
  TraceZobelSpkrBoxZMag, TraceZobelSpkrBoxZPhase, // Zobel + spkr in box
// Traces Transmission
  TraceLowUpMag, TraceLowUpPhase,         // LowUp filter only
  TraceCompMag, TraceCompPhase,           // Compensator only
  TraceLowUpCompMag, TraceLowUpCompPhase, // LowUp + Compensator
  TraceVelocityMag, TraceVelocityPhase,   // Spkr in box to Cone Velocity
// Traces Outputs
  TraceSplMag, TraceSplPhase,             // Spkrbox acoustcal
  TraceSpkrXMag, TraceSpkrXPhase,         // Membrane excursion
  TraceAmpVMag, TraceAmpVPhase,           // Amplifier output Voltage
  TraceAmpIMag, TraceAmpIPhase: tTrace;   // Amplifier output Current x 10

// TraceColors
  Tc1, Tc2, Tc3, Tc4, Tc5, Tc6, Tc7, Tc8, Tc9, Tc10, Tc11, Tc12: tColor;

// Calculations
  InputLevel: real;
  StartFrequency, nDecades: integer;
  Frequency, Sd, Bl,
    Re, Le, Cms, Lcms, Mms, Cmms, Rms, Rrms, Vbx, Cbx, Lcbx, Rbx, Rrbx,
  	Rco1, Rco2, Cco1, Rgy1, Rgy2, Cgy, Lgy, DipFreq,
  	Rlu1, Rlu2, Rlu3, Clu1, Rzo1, Czo1, Lzo1, Rzo2, Czo2: real;
  	xLe, xLcms, xCmms, xLcbx, xCco1, xLgy,
    xClu1, xCzo1, xLzo1, xCzo2: complex;
  UseCompensator, UseLowUp, UseZobel: boolean;

function FRval(InpStr: string): double;
  var Temp: real;
  begin
  	val(InpStr, Temp);
  	FRval:= Temp;
  end;

procedure CalculateGyrator;
var Qcomp: real;
begin
 	Lgy:= Cgy * Rgy1 * Rgy2;
  Frm_BassCalc.TbxLgy.text:= Format('%6.2f', [Lgy]);
  if (Lgy = 0) or (Cco1 = 0) or ((Rgy1 + Rco1) = 0) then exit;
  DipFreq:= 1 / (2 * pi * sqrt(Lgy * Cco1));
  Frm_BassCalc.TbxDipfreq.text:= Format('%6.2f', [DipFreq]);
  Qcomp:= sqrt(Lgy / Cco1) / (Rgy1 + Rco1);
  Frm_BassCalc.TbxQcomp.text:= format('%5.3f', [Qcomp]);
end;

procedure Calculate;
const RhoAir = 1.2; // kg/m3
var
  I: integer;
  Z1, ZReLe, ZSpkrMech, ZSpkrInAir, ZSpkrInBox, Zzobel, ZZobelSpkr, TrLu,
  TrComp, AmpVOut, AmpIout, TrVelocity, SpkrX, Vflow, Pressure: complex;
begin
  CalculateGyrator;
  for I:= 0 to nFrequencies do
	begin
  	Frequency:= Ten2Power(I * nDecades / nFrequencies)* StartFrequency;
    // Convert frequency dependent components to complex
   	xLe:= cpxL(Le, Frequency);
  	xLcms:= cpxL(Lcms, Frequency);
  	xCmms:= cpxC(Cmms, Frequency);
   	xLcbx:= cpxL(Lcbx, Frequency);
   	xCco1:= cpxC(Cco1, Frequency);
  	xLgy:= cpxL(Lgy, Frequency);
    xClu1:= cpxC(Clu1, Frequency);
    xCzo1:= cpxC(Czo1, Frequency);
    xLzo1:= cpxL(Lzo1, Frequency);
    xCzo2:= cpxC(Czo2, Frequency);
    // Speaker electrical and mechanical impedances
    ZReLe:= Re + xLe;
    ZSpkrMech:= xLcms and xCmms and Rrms;
    ZSpkrInAir:= ZReLe + ZSpkrMech;
    ZSPkrInBox:= ZReLe + (ZSpkrMech and xLcbx and Rrbx);
    TraceSpeakerZMag[I]:= cpxMag(ZSpkrInAir);
    TraceSpeakerZPhase[I]:= cpxPhaseDeg(ZSpkrInAir);
    TraceSpeakerBoxZMag[I]:= cpxMag(ZSpkrInBox);
    TraceSpeakerBoxZPhase[I]:= cpxPhaseDeg(ZSpkrInBox);
    // Zobel network
    Zzobel:= (Rzo2 + xCzo2) and (Rzo1 + xLzo1 + xCzo1);
    TraceZobelZMag[I]:= cpxMag(Zzobel);
    TraceZobelZPhase[I]:= cpxPhaseDeg(Zzobel);
    ZZobelSpkr:= Zzobel and ZSpkrInBox; // zobel + spkr + box
    TraceZobelSpkrBoxZMag[I]:= cpxMag(ZZobelSpkr);
    TraceZobelSpkrBoxZPhase[I]:= cpxPhaseDeg(ZZobelSpkr);
    // Transmission LowUp filter
    TrLu:= (Rlu2 + (Rlu3 and xClu1)) / Rlu1;
    TraceLowUpMag[I]:= cpxdBMag(TrLu);
    TraceLowUpPhase[I]:= cpxPhaseDeg(TrLu);
    // Transmission Compensator
    TrComp:= 1 + Rco2 / ((Rco1 + xCco1) and (Rgy1 + xLgy));
    TrComp:= TrComp / (1 + Rco2 / Rco1); // compensate gain
    TraceCompMag[I]:= cpxdBMag(TrComp);
    TraceCompPhase[I]:= cpxPhaseDeg(TrComp);
    // Transmission Compensator + Low-Up
    TraceLowUpCompMag[I]:= cpxdBMag(TrComp * TrLu);
    TraceLowUpCompPhase[I]:= cpxPhaseDeg(TrComp * TrLu);
    // Amplifier Output
    AmpVOut:= InputLevel;
    if UseCompensator then AmpVOut:= AmpVOut * TrComp;
    if UseLowUp then AmpVOut:= AmpVOut * TrLu;
    TraceAmpVMag[I]:= cpxMag(AmpVOut);
    TraceAmpVPhase[I]:= cpxPhaseDeg(AmpVOut);
    if UseZobel then ZSpkrInBox:= ZSpkrInBox and ZZobel;
    AmpIOut:= AmpVout / ZSpkrInBox;
    TraceAmpIMag[I]:= cpxMag(AmpIOut) * 10;
    TraceAmpIPhase[I]:= cpxPhaseDeg(AmpIOut / AmpVOut);
    // Transmission from Amplifier output to Cone velocity
    Z1:= ZSpkrMech and xLcbx and Rrbx; // add box Compliance and damping
    TrVelocity:= Z1 / (ZReLe + Z1); // Transmission as V/V in model
    TrVelocity:= TrVelocity / Bl * AmpVout; // in m/sec @ amplifier outputlevel
    TraceVelocityMag[I]:= cpxMag(TrVelocity) * 100; // in cm/sec
    TraceVelocityPhase[I]:= cpxPhaseDeg(TrVelocity);
    // Cone excursion @ Amplifier Output level
    SpkrX:= cpxIntegrate(TrVelocity, Frequency);
    TraceSpkrXMag[I]:= cpxMag(SpkrX) * 10000; // to 0.1 mm
    TraceSpkrXPhase[I]:= cpxPhaseDeg(SpkrX);
    // Volume flow in m3/s
		Vflow:= Sd * TrVelocity;
    // Sound Pressure at 1 m distance according to Beranek / Linkwitz
    Pressure:= Vflow * Frequency * RhoAir;
    TraceSplMag[I]:= 20 * log10(cpxMag(Pressure) / 2E-5); // in dB
    TraceSplPhase[I]:= cpxPhaseDeg(Pressure);
  end;
end;

procedure Plot;
begin
	if SystemReady then with FrmLogGraph do with Frm_BassCalc do
	begin
		InitLogDisplay(StartFrequency, nDecades, @Plot);
    // Impedances
    if CbxShowZspkr.checked then
    begin
			DispAmplitudeTrace(TraceSpeakerZMag, nFrequencies, Tc1);
			DispPhaseTrace(TraceSpeakerZPhase, nFrequencies, Tc1);
    end;
    if CbxShowZspkrInBox.checked then
    begin
			DispAmplitudeTrace(TraceSpeakerBoxZMag, nFrequencies, Tc2);
			DispPhaseTrace(TraceSpeakerBoxZPhase, nFrequencies, Tc2);
    end;
    if CbxShowZzobel.checked then
    begin
			DispAmplitudeTrace(TraceZobelZMag, nFrequencies, Tc3);
			DispPhaseTrace(TraceZobelZPhase, nFrequencies, Tc3);
    end;
    if CbxShowZzobelSpkrBox.checked then
    begin
			DispAmplitudeTrace(TraceZobelSpkrBoxZMag, nFrequencies, Tc4);
			DispPhaseTrace(TraceZobelSpkrBoxZPhase, nFrequencies, Tc4);
    end;

    // Transmissions
    if CbxShowLowUp.checked then
    begin
			DispAmplitudeTrace(TraceLowUpMag, nFrequencies, Tc5);
			DispPhaseTrace(TraceLowUpPhase, nFrequencies, Tc5);
    end;
    if CbxShowComp.checked then
    begin
			DispAmplitudeTrace(TraceCompMag, nFrequencies, Tc6);
			DispPhaseTrace(TraceCompPhase, nFrequencies, Tc6);
    end;
    if CbxShowAll.checked then
    begin
			DispAmplitudeTrace(TraceLowUpCompMag, nFrequencies, Tc7);
			DispPhaseTrace(TraceLowUpCompPhase, nFrequencies, Tc7);
    end;

    // Outputs
    if CbxShowConeVelocity.checked then
    begin
			DispAmplitudeTrace(TraceVelocityMag, nFrequencies, Tc8);
			DispPhaseTrace(TraceVelocityPhase, nFrequencies, Tc8);
    end;
    if CbxShowSpeakerX.checked then
    begin
			DispAmplitudeTrace(TraceSpkrXMag, nFrequencies, Tc9);
			DispPhaseTrace(TraceSpkrXPhase, nFrequencies, Tc9);
    end;
    if CbxShowSpl.checked then
    begin
			DispAmplitudeTrace(TraceSplMag, nFrequencies, Tc10);
			DispPhaseTrace(TraceSplPhase, nFrequencies, Tc10);
    end;
    if CbxShowAmpV.checked then
    begin
			DispAmplitudeTrace(TraceAmpVMag, nFrequencies, Tc11);
			DispPhaseTrace(TraceAmpVPhase, nFrequencies, Tc11);
    end;
    if CbxShowAmpI.checked then
    begin
			DispAmplitudeTrace(TraceAmpIMag, nFrequencies, Tc12);
			DispPhaseTrace(TraceAmpIPhase, nFrequencies, Tc12);
    end;
  end;
end;

procedure TFrm_BassCalc.ButtReCalculateClick(Sender: TObject);
begin
	Calculate;
	Plot;
end;

procedure CalcCtot; // total Compliance
var Ctot, Qtc, Lctot: real;
begin
  if (Cms + Cbx) = 0 then exit; // prevent division by zero
  Ctot := Cms * Cbx / (Cms + Cbx);
  Frm_BassCalc.TbxCtot.text:= Format('%6.2f', [Ctot * 1000]); // m/N to mm/N
  Lctot:= Ctot * Bl * Bl;
  Frm_BassCalc.TbxLctot.text:= Format('%6.2f', [Lctot * 1000]); // Henry to mH
  if (Lctot = 0) then exit; // prevent division by zero
  Qtc:= sqrt(Cmms / Lctot) * Parallel(Re , Rrms);
  Frm_BassCalc.TbxQtc.text:= Format('%5.3f', [Qtc]);
end;

// Editing or stuffing a value in a textbox triggers the change event for that box.
// The change event handler then puts the value in the corresponding variable.
// When the value is (temporary) zero a possible division by zero is skipped.

// LoudSpeaker
procedure TFrm_BassCalc.TbxSdChange(Sender: TObject);
begin
	Sd:= FRval(TbxSD.text)/10000; // cm2 to m2
end;

procedure TFrm_BassCalc.TbxBlChange(Sender: TObject);
begin
  Bl:= FRval(TbxBl.text);
  if Bl = 0 then Bl:= 1; // prevent division by zero elsewhere
  CalcCtot;
end;

procedure TFrm_BassCalc.TbxReChange(Sender: TObject);
begin
  Re:= FRval(TbxRe.text);
  CalcCtot;
end;

procedure TFrm_BassCalc.TbxLeChange(Sender: TObject);
begin
  Le:= FRval(TbxLe.text) / 1000; // mH to Henry
end;

procedure TFrm_BassCalc.TbxCmsChange(Sender: TObject);
begin
  Cms:= FRval(TbxCms.text) / 1000; // mm/N to m/N
  Lcms:= Cms * Bl * Bl;
  TbxLcms.text:= Format('%6.2f', [Lcms * 1000]); // Henry to mH
  CalcCtot;
end;

procedure TFrm_BassCalc.TbxInputLevelChange(Sender: TObject);
begin
  InputLevel:= FRval(TbxInputLevel.text);
end;

procedure TFrm_BassCalc.TbxMmsChange(Sender: TObject);
begin
  Mms:= FRval(TbxMms.text) / 1000; // gram to kg
  if Bl <> 0 then Cmms:= Mms / Bl / Bl;
  TbxCmms.text:= Format('%6.2f', [Cmms * 1000]);  // Farad to mF
  CalcCtot;
end;

procedure TFrm_BassCalc.TbxRmsChange(Sender: TObject);
begin
  Rms:= FRval(TbxRms.text);
  if Rms <> 0 then Rrms:= Bl * Bl / Rms; // prevent division by zero
  TbxRrms.text:= Format('%6.2f', [Rrms]);
  CalcCtot;
end;

// Box
procedure TFrm_BassCalc.TbxVbxChange(Sender: TObject);
begin
	Vbx:= FRval(TbxVbx.text) / 1000; // to m3
  if Sd <> 0 then Cbx:= Vbx / Sd / Sd * 7.14E-6;
  TbxCbx.text:= Format('%6.2f', [Cbx * 1000]); // m/N to mm/N
  Lcbx:= Cbx * Bl * Bl;
  TbxLcbx.text:= Format('%6.2f', [Lcbx * 1000]); // Farad to mF
  CalcCtot;
end;

procedure TFrm_BassCalc.TbxRbxChange(Sender: TObject);
begin
  Rbx:= FRval(TbxRbx.text);
  if Rbx <> 0 then Rrbx:= Bl * Bl / Rbx; // prevent division by zero
  TbxRrbx.text:= Format('%6.2f', [Rrbx]);
end;

// Compensator
procedure TFrm_BassCalc.TbxRco1Change(Sender: TObject);
begin
  Rco1:= FRval(TbxRco1.text) * 1000; // kOhm to Ohm
  CalculateGyrator;
end;

procedure TFrm_BassCalc.TbxRco2Change(Sender: TObject);
begin
  Rco2:= FRval(TbxRco2.text) * 1000; // kOhm to Ohm
  CalculateGyrator;
end;

procedure TFrm_BassCalc.TbxCco1Change(Sender: TObject);
begin
  Cco1:= FRval(TbxCco1.text) / 1E6; // uF to Farad
  CalculateGyrator;
end;

procedure TFrm_BassCalc.TbxRgy1Change(Sender: TObject);
begin
  Rgy1:= FRval(TbxRgy1.text) * 1000; // kOhm to Ohm
  CalculateGyrator;
end;

procedure TFrm_BassCalc.TbxRgy2Change(Sender: TObject);
begin
  Rgy2:= FRval(TbxRgy2.text) * 1000; // kOhm to Ohm
  CalculateGyrator;
end;

procedure TFrm_BassCalc.TbxCgyChange(Sender: TObject);
begin
  Cgy:= FRval(TbxCgy.text) / 1E9; // nF to Farad
  CalculateGyrator;
end;

procedure TFrm_BassCalc.CbxUseCompensatorChange(Sender: TObject);
begin
  UseCompensator:= CbxUseCompensator.Checked;
  Calculate;
  Plot;
end;

// Low-Up filter
procedure TFrm_BassCalc.TbxRlu1Change(Sender: TObject);
begin
  Rlu1:= FRval(TbxRlu1.text) * 1000; // kOhm to Ohm
end;

procedure TFrm_BassCalc.TbxRlu2Change(Sender: TObject);
begin
  Rlu2:= FRval(TbxRlu2.text) * 1000; // kOhm to Ohm
end;

procedure TFrm_BassCalc.TbxRlu3Change(Sender: TObject);
begin
	Rlu3:= FRval(TbxRlu3.text) * 1000; // kOhm to Ohm
end;

procedure TFrm_BassCalc.TbxClu1Change(Sender: TObject);
begin
	Clu1:= FRval(TbxClu1.text) / 1E9; // nF to Farad
end;

procedure TFrm_BassCalc.CbxUseLowUpChange(Sender: TObject);
begin
  UseLowUp:= CbxUseLowUp.Checked;
  Calculate;
  Plot;
end;

// Zobel
procedure TFrm_BassCalc.TbxRzo1Change(Sender: TObject);
begin
  Rzo1:= FRval(TbxRzo1.text); // Ohm
end;

procedure TFrm_BassCalc.TbxLzo1Change(Sender: TObject);
begin
  Lzo1:= FRval(TbxLzo1.text) / 1000; // mH to Henry
end;

procedure TFrm_BassCalc.TbxCzo1Change(Sender: TObject);
begin
	Czo1:= FRval(TbxCzo1.text) / 1E6; // uF to Farad
end;

procedure TFrm_BassCalc.TbxRzo2Change(Sender: TObject);
begin
	Rzo2:= FRval(TbxRzo2.text); // Ohm
end;

procedure TFrm_BassCalc.TbxCzo2Change(Sender: TObject);
begin
	Czo2:= FRval(TbxCzo2.text) / 1E6; // uF to Farad
end;

procedure TFrm_BassCalc.CbxUseZobelChange(Sender: TObject);
begin
  UseZobel:= CbxUseZobel.Checked;
  Calculate;
  Plot;
end;

// Frequencies
procedure TFrm_BassCalc.RbRange1Change(Sender: TObject);
begin
  StartFrequency:= 1;
  nDecades:= 2;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange2Change(Sender: TObject);
begin
  StartFrequency:= 1;
  nDecades:= 3;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange3Change(Sender: TObject);
begin
	StartFrequency:= 10;
	nDecades:= 1;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange4Change(Sender: TObject);
begin
	StartFrequency:= 10;
	nDecades:= 2;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange5Change(Sender: TObject);
begin
	StartFrequency:= 10;
	nDecades:= 3;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange6Change(Sender: TObject);
begin
	StartFrequency:= 100;
	nDecades:= 1;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange7Change(Sender: TObject);
begin
	StartFrequency:= 100;
	nDecades:= 2;
  Calculate;
	Plot;
end;

procedure TFrm_BassCalc.RbRange8Change(Sender: TObject);
begin
	StartFrequency:= 1000;
	nDecades:= 1;
  Calculate;
	Plot;
end;

// Show
procedure TFrm_BassCalc.CbxShowZspkrChange(Sender: TObject);
begin
	Plot;
end;

procedure TFrm_BassCalc.CbxShowZspkrInBoxChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowZzobelChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowZzobelSpkrBoxChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowConeVelocityChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowLowUpChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowCompChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowHighUpChange(Sender: TObject);
begin
  Plot
end;

procedure TFrm_BassCalc.CbxShowAllChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowSplChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowSpeakerXChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowAmpIChange(Sender: TObject);
begin
  Plot;
end;

procedure TFrm_BassCalc.CbxShowAmpVChange(Sender: TObject);
begin
  Plot;
end;

// Trace Colors
procedure TFrm_BassCalc.LblSelColor1Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc1:= ColorDialog.Color;
  Plot;
  LblSelColor1.Color:= Tc1;
end;

procedure TFrm_BassCalc.LblSelColor2Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc2:= ColorDialog.Color;
  Plot;
  LblSelColor2.Color:= Tc2;
end;

procedure TFrm_BassCalc.LblSelColor3Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc3:= ColorDialog.Color;
  Plot;
  LblSelColor3.Color:= Tc3;
end;

procedure TFrm_BassCalc.LblSelColor4Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc4:= ColorDialog.Color;
  Plot;
  LblSelColor4.Color:= Tc4;
end;

procedure TFrm_BassCalc.LblSelColor5Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc5:= ColorDialog.Color;
  Plot;
  LblSelColor5.Color:= Tc5;
end;

procedure TFrm_BassCalc.LblSelColor6Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc6:= ColorDialog.Color;
  Plot;
  LblSelColor6.Color:= Tc6;
end;

procedure TFrm_BassCalc.LblSelColor7Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc7:= ColorDialog.Color;
  Plot;
  LblSelColor7.Color:= Tc7;
end;

procedure TFrm_BassCalc.LblSelColor8Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc8:= ColorDialog.Color;
  Plot;
  LblSelColor8.Color:= Tc8;
end;

procedure TFrm_BassCalc.LblSelColor9Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc9:= ColorDialog.Color;
  Plot;
  LblSelColor9.Color:= Tc9;
end;

procedure TFrm_BassCalc.LblSelColor10Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc10:= ColorDialog.Color;
  Plot;
  LblSelColor10.Color:= Tc10;
end;

procedure TFrm_BassCalc.LblSelColor11Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc11:= ColorDialog.Color;
  Plot;
  LblSelColor11.Color:= Tc11;
end;

procedure TFrm_BassCalc.LblSelColor12Click(Sender: TObject);
begin
  if ColorDialog.Execute then Tc12:= ColorDialog.Color;
  Plot;
  LblSelColor12.Color:= Tc12;
end;

procedure DefaultColors;
begin
  Tc1:= clFuchsia;
  Tc2:= clYellow;
  Tc3:= clLime;
  Tc4:= clAqua;
  Tc5:= clLime;
  Tc6:= clCream;
  Tc7:= clSilver;
  Tc8:= clFuchsia;
  Tc9:= clLime;
  Tc10:= clRed;
  Tc11:= clAqua;
  Tc12:= clYellow;
  with Frm_BassCalc do
  begin
  	LblSelColor1.Color:= Tc1;
  	LblSelColor2.Color:= Tc2;
  	LblSelColor3.Color:= Tc3;
  	LblSelColor4.Color:= Tc4;
  	LblSelColor5.Color:= Tc5;
  	LblSelColor6.Color:= Tc6;
  	LblSelColor7.Color:= Tc7;
  	LblSelColor8.Color:= Tc8;
  	LblSelColor9.Color:= Tc9;
  	LblSelColor10.Color:= Tc10;
  	LblSelColor11.Color:= Tc11;
  	LblSelColor12.Color:= Tc12;
  end;
end;

procedure TFrm_BassCalc.ButtDefaultColorsClick(Sender: TObject);
begin
  DefaultColors;
end;

// General, Files

function StripPath(Path: string): string;
var N: Integer;
begin
for N := Length(Path) downto 1 do
  if Path[N] = Slash then
  begin
    StripPath:= Midstr(Path, N + 1, Length(Path));
    Exit;
  end;
If N <= 1 then StripPath:= Path;
end;

procedure ReadSettings (FileName: string);
var DataFile: text;
begin
	assign(DataFile, FileName);
	reset(DataFile);
	readln(DataFile, Sd);
	readln(DataFile, Bl);
	readln(DataFile, Re);
	readln(DataFile, Le);
	readln(DataFile, Cms);
	readln(DataFile, Mms);
	readln(DataFile, Rms);
	readln(DataFile, Vbx);
  readln(DataFile, Rbx);
	readln(DataFile, Rco1);
	readln(DataFile, Rco2);
	readln(DataFile, Cco1);
	readln(DataFile, Rgy1);
	readln(DataFile, Rgy2);
	readln(DataFile, Cgy);
	readln(DataFile, Rlu1);
	readln(DataFile, Rlu2);
	readln(DataFile, Rlu3);
	readln(DataFile, Clu1);
	readln(DataFile, Rzo1);
	readln(DataFile, Lzo1);
	readln(DataFile, Czo1);
	readln(DataFile, Rzo2);
	readln(DataFile, Czo2);
	with Frm_BassCalc do
  begin
    TbxSd.text:= Format('%6.2f', [Sd * 10000]);    // to cm2
	  TbxBl.text:= Format('%6.2f', [Bl]);            // N/A
	  TbxRe.text:= Format('%6.2f', [Re]);            // Ohm
	  TbxLe.text:= Format('%6.2f', [Le * 1000]);     // to mH
	  TbxCms.text:= Format('%6.2f', [Cms * 1000]);   // to mm/N
	  TbxMms.text:= Format('%6.2f', [Mms * 1000]);   // to gram
	  TbxRms.text:= Format('%6.2f', [Rms]);          // kg/sec
	  TbxVbx.text:= Format('%6.2f', [Vbx * 1000]);   // to dm3
	  TbxRbx.text:= Format('%6.2f', [Rbx]);          // Ohm
	  TbxRco1.text:= Format('%6.2f', [Rco1 / 1000]); // to kOhm
	  TbxRco2.text:= Format('%6.2f', [Rco2 / 1000]); // to kOhm
	  TbxCco1.text:= Format('%6.2f', [Cco1 * 1E6]);  // to uF
	  TbxRgy1.text:= Format('%6.2f', [Rgy1 / 1000]); // to kOhm
  	TbxRgy2.text:= Format('%6.2f', [Rgy2 / 1000]); // to kOhm
 	  TbxCgy.text:= Format('%6.2f', [Cgy * 1E9]);    // to nF
    TbxRlu1.text:= Format('%6.2f', [Rlu1 / 1000]); // to kOhm
    TbxRlu2.text:= Format('%6.2f', [Rlu2 / 1000]); // to kOhm
    TbxRlu3.text:= Format('%6.2f', [Rlu3 / 1000]); // to kOhm
    TbxClu1.text:= Format('%6.2f', [Clu1 * 1E9]);  // to nF
    TbxRzo1.text:= Format('%6.2f', [Rzo1]);        // Ohm
	  TbxLzo1.text:= Format('%6.2f', [Lzo1 * 1000]); // to mH
	  TbxCzo1.text:= Format('%6.2f', [Czo1 * 1E6]);  // to uF
	  TbxRzo2.text:= Format('%6.2f', [Rzo2]);        // Ohm
	  TbxCzo2.text:= Format('%6.2f', [Czo2 * 1E6]);  // to uF
    Application.ProcessMessages;
  end;
  close(DataFile);
end;

procedure WriteSettings (FileName: string);
var DataFile: text;
begin
	assign(DataFile, FileName);
	rewrite(DataFile);
	writeln(DataFile, Sd);
	writeln(DataFile, Bl);
	writeln(DataFile, Re);
	writeln(DataFile, Le);
	writeln(DataFile, Cms);
	writeln(DataFile, Mms);
	writeln(DataFile, Rms);
	writeln(DataFile, Vbx);
  writeln(DataFile, Rbx);
	writeln(DataFile, Rco1);
	writeln(DataFile, Rco2);
	writeln(DataFile, Cco1);
	writeln(DataFile, Rgy1);
	writeln(DataFile, Rgy2);
	writeln(DataFile, Cgy);
	writeln(DataFile, Rlu1);
	writeln(DataFile, Rlu2);
	writeln(DataFile, Rlu3);
	writeln(DataFile, Clu1);
	writeln(DataFile, Rzo1);
	writeln(DataFile, Lzo1);
	writeln(DataFile, Czo1);
	writeln(DataFile, Rzo2);
	writeln(DataFile, Czo2);
	close(DataFile);
end;

procedure TFrm_BassCalc.ButtSaveAsClick(Sender: TObject);
begin
  with SaveDialog do
  begin
  Title:= 'Save parameters as: ';
  DefaultExt:= 'param';
  Filter:= 'Parameter files|*.param'; // allow only this filetype
  if Execute then ParameterFileName:= Filename;
	if ParameterFilename <> '' then WriteSettings(ParameterFileName);
  end;
end;

procedure TFrm_BassCalc.ButtOpenClick(Sender: TObject);
begin
  OpenDialog:= TOpenDialog.Create(self);
  OpenDialog.InitialDir:= GetCurrentDir; // start in dir of executable
  OpenDialog.Options:= [ofFileMustExist]; // only allow existing files
  OpenDialog.Filter:= 'Parameter files|*.param'; // only allow .param files
  OpenDialog.FilterIndex:= 1; // there is only one choise
  if OpenDialog.Execute then  // display the dialog
  begin
    ParameterFilename:= OpenDialog.FileName;
  	TopText:= VersionString + ' ....' + Slash + StripPath(ParameterFileName);
    Caption:= TopText;
  end;
  OpenDialog.Free; // Free up the dialog
  if ParameterFilename <> '' then
  begin
    ReadSettings(ParameterFileName);
    Calculate;
    Plot;
  end;
end;

procedure TFrm_BassCalc.ButtDefaultsClick(Sender: TObject);
begin
	// for ScanSpeak 30W/4558T00 in a 41 liter undamped box
	TbxSd.text:= '466';
	TbxBl.text:= '10.5';
	TbxRe.text:= '2.6';
	TbxLe.text:= '0.83';
	TbxCms.text:= '0.65';
	TbxMms.text:= '135';
	TbxRms.text:= '2.88';
	TbxVbx.text:= '41';
  TbxRbx.text:= '0.011';
	TbxRco1.text:= '3.9';
	TbxRco2.text:= '10';
	TbxCco1.text:= '1.2';
	TbxRgy1.text:= '0.27';
	TbxRgy2.text:= '100';
	TbxCgy.text:= '470';
	TbxRlu1.text:= '10';
	TbxRlu2.text:= '10';
	TbxRlu3.text:= '100';
	TbxClu1.text:= '330';
	TbxRzo1.text:= '2.6';
	TbxLzo1.text:= '12.3';
	TbxCzo1.text:= '1200';
	TbxRzo2.text:= '2.6';
	TbxCzo2.text:= '120';
	TbxInputLevel.Text:= '2.0'; // change to 2.83 for 8 Ohm loudspeakers
	if SystemReady then
	begin
		Calculate;
		Plot;
	end;
end;

procedure TFrm_BassCalc.Timer1Timer(Sender: TObject);
const counts: integer = 2;
begin
  RbRange2Change(Sender);
  ButtReCalculateClick(Sender);
  dec(counts);
  if counts = 0 then Timer1.Enabled := false;
end;

procedure TFrm_BassCalc.FormCreate(Sender: TObject);
begin
  // prevent problems regarding locale with ',' as separator
  DefaultFormatSettings.DecimalSeparator:= '.';
	{$IFDEF Linux}
  	Slash:= '/';
	{$ELSE}
  	{$IFDEF WINDOWS}
    	Slash:= '\';
  	{$ENDIF}
    {tbd: mac}
	{$ENDIF}
  Application.CreateForm(TFrmLogGraph, FrmLogGraph); // see BassCalc for comment
  ButtDefaultsClick(Sender);
  DefaultColors;
  TopText:= VersionString + ' .... Defaults';
  Caption:= TopText;
  SystemReady:= true;
  Timer1.Enabled:= true;
end;

{$R *.lfm}

end.

