Pascal Source Code for the Kameoka and Kuriyagawa model for sensory dissonance

UNIT FKK;
{----------------------------------------------------------------------}
{  Source of unit that implements the Kameoka and Kuriyagawa model for }
{  sensory dissonance, according to their articles in The Journal of   }
{  the Acoustical Society of America, volume 45, number 6, 1969,       }
{  pages 1451 to 1469. Pascal text for Borland's Turbo Pascal compi-   }
{  ler, version 4.0. This file implements the following:               }
{                                                                      }
{    CONSTANTS : KK_number_max                                         }
{                KK_version                                            }
{        TYPES : KK_spectrum                                           }
{                KK_parameters                                         }
{    VARIABLES : KK_conditions                                         }
{   PROCEDURES : KK_set_default_conditions                             }
{                KK_set_conditions                                     }
{                KK_show_conditions                                    }
{                KK_show_stimulus (stimulus,format)                    }
{                KK_show_info                                          }
{                KK_checks                                             }
{                KK_preprocess (raw, sorted)                           }
{    FUNCTIONS : KK_dissonance (stimulus)                              }
{                KK_fb (f1, L)                                         }
{                   LOG (a)                                            }
{                   POWER (a, b)                                       }
{                   ubar_to_dBspl (ubar)                               }
{                   dBspl_to_ubar (dBspl)                              }
{                                                                      }
{   Before using these functions and procedures, first initialize the  }
{   variable KK_conditions and call KK_set_conditions, which then up-  }
{   dates the derived parameters (in the same global variable). Never  }
{   change the derived parameters directly. You may also call the pro- }
{   cedure KK_set_default_conditions to initialize and update. Global  }
{   variable KK_conditions influences almost all functions and proce-  }
{   dures. So be sure you initialized and updated correctly.           }
{                                                                      }
{                Pieter Suurmond, 5 march 1994.                        }
{----------------------------------------------------------------------}
INTERFACE

USES Dos, Crt;

CONST KK_version = '1.06 (13 march 1994)';
      KK_number_max = 80;    { Maximum number of frequency components. }

TYPE KK_spectrum = RECORD
          number : BYTE;                            { Number partials. }
           Hertz : ARRAY[0..KK_number_max] OF REAL; { Frequency.       }
            ubar : ARRAY[0..KK_number_max] OF REAL; { Sound pressure.  }
             rad : ARRAY[0..KK_number_max] OF REAL; { Phase.           }
                    END;

     KK_parameters = RECORD
   { Primary parameters. These may be set or adjusted by the user.     }
   { Always call the procedure KK_set_conditions after changing one of }
   { these (except for Lfb, fl and fh, (the only 3!) which may be      }
   { changed without updating the derived parameters).                 }
{ For the actual model:                                                }
            kk0 : REAL;    { Noise conditions at auditorium.           }
            CC0 : REAL;
             k0 : REAL;
             C0 : REAL;
             nh : REAL;    { Exponents for sound pressure correction.  }
             nl : REAL;
             ne : REAL;
            p57 : REAL;    { Sound pressure (microbar) at 57 dB SPL.   }
           beta : REAL;    { Extended power law constant.              }
             Lm : REAL;    { Masking level difference (dB SPL).        }
            Lfb : REAL;    { Fixed level (dBspl) to be used by KK-dis- }
                           { sonance at fb calculation. When Lfb = -1, }
                           { the average dB level is used.             }
{ For the preprocessor:                                                }
          match : REAL;    { Deviation (cents) identical frequencies.  }
             fl : REAL;    { Bandpass low frequency (Hertz).           }
             fh : REAL;    { Bandpass high frequency (Hertz).          }
          slope : REAL;    { Bandpass cutoff slopes (dBspl/octave).    }
       treshold : REAL;    { Partials below this (dB SPL) are trimmed  }
                           { (equal freqs will be added first).        }
   { Derived parameters. These are calculated from the preceeding ones }
   { by the procedure KK_set_conditions. Never change them directly.   }
   {                          For the actual model:                    }
                p0 : REAL;  { Derived from p57. Speeds up dB <-> ubar. }
             ne_nh : REAL;  { Used at function D2i.                    }
             ne_nl : REAL;
            p57_ne : REAL;
          beta_inv : REAL;
              DDIn : REAL;  { Dissonance intensity of noises.          }
      Dn0_beta_inv : REAL;  { Dn0^(1/beta), used by function Dm.       }
            k0_kk0 : REAL;  { Used by KK_dissonance.                   }
            mask_r : REAL;  { Pressure ratio perfect masking.          }
        mask_r_inv : REAL;  { Inverse Pressure ratio perfect masking.  }
   {                          For the preprocessor:                    }
               dev : REAL;  { Deviation (fraction) equal frequencies.  }
             expon : REAL;  { Exponent derived from bandpass-slope.    }
                pt : REAL;  { Derived from treshold.                   }
                     END;

VAR KK_conditions : KK_parameters;  { Global var. to store conditions. }

PROCEDURE KK_set_default_conditions;
PROCEDURE KK_set_conditions;
PROCEDURE KK_show_conditions;
PROCEDURE KK_show_stimulus (VAR S : KK_spectrum; format : BYTE);
PROCEDURE KK_show_info;
PROCEDURE KK_checks;
PROCEDURE KK_preprocess (VAR Stim, Stim_sorted : KK_spectrum);

FUNCTION KK_dissonance (VAR stim : KK_spectrum) : REAL;
FUNCTION KK_fb (f1, L : REAL) : REAL;
FUNCTION ubar_to_dBspl (ubar : REAL) : REAL;
FUNCTION dBspl_to_ubar (dBspl : REAL) : REAL;
FUNCTION POWER (X: REAL; Y: REAL): REAL;
FUNCTION LOG (X : REAL) : REAL;

{----------------------------------------------------------------------}
IMPLEMENTATION

PROCEDURE Message (S : STRING);
          FORWARD;

{----------------------------- BASIC ARITHMETIC: ----------------------}
FUNCTION POWER (X: REAL; Y: REAL): REAL;
{        Raises X to the Yth power.                                    }
{        X may not be negative. If X is 0, then Y must be positive.    }
BEGIN
     IF X > 0 THEN
        POWER := EXP (Y * LN (X))
     ELSE
        IF X < 0  THEN
           Message ('Error: Cannot raise negative numbers.')
        ELSE                                               { Thus X=0. }
           IF Y > 0
              THEN POWER := 0
           ELSE
              Message ('Error: Cannot raise 0 to non-positive numbers.')
END;

FUNCTION LOG (X : REAL) : REAL;
{        Calculates the 10 log of X.  X must be greater than 0.        }
CONST    LN10 = 2.302585093;
BEGIN
     IF X <= 0 THEN
        Message ('Error: Logarithm of a non-positive number.')
     ELSE
        LOG := LN (X) / LN10
END;

{------------------------ SOUND PRESSURE LEVELS: ----------------------}
FUNCTION ubar_to_dBspl (ubar : REAL): REAL;
BEGIN
     ubar_to_dBspl := 20 * LOG (ubar/KK_conditions.p0)
END;

FUNCTION dBspl_to_ubar (dBspl : REAL): REAL;
BEGIN
     dBspl_to_ubar := KK_conditions.p0 * POWER (10, dBspl/20)
END;

{------------------------ CALCULATION CONDITIONS: ---------------------}
PROCEDURE KK_set_conditions;
{         Calculates and sets derived parameters in global variable    }
{         KK_conditions. Call this procedure after you changed one or  }
{         more of the primary parameters in KK_conditions.             }
BEGIN
  WITH KK_conditions DO
       BEGIN
     { For the model: }
       p0 := p57 * POWER (10, -57/20);      { For level conversions.   }
       ne_nh := ne - nh;
       ne_nl := ne - nl;
       p57_ne := POWER (p57, -ne);
       beta_inv := 1 / beta;
       DDIn := POWER (CC0, beta_inv);
       Dn0_beta_inv := POWER (k0*C0, beta_inv);
       k0_kk0 := k0 / kk0;                  { Used by KK_dissonance.   }
       mask_r := POWER (10, Lm/20);         { Pressure ratios at which }
       mask_r_inv := 1 / mask_r;            { perfect masking occurs.  }
     { For the preprocessor: }
       pt := dBspl_to_ubar (treshold);      { Convert to linear scale. }
       dev := -1 + POWER (2, (match/1200)); { cents to fractions.      }
       expon := slope / (20 * LOG(2));      { dB/octave to frequency-  }
       END;                                 { ratio-exponent.          }
END;

PROCEDURE KK_set_default_conditions;
{         Sets the default calculation conditions of the model in the  }
{         KK_conditions (it's thus destructive). You don't need to     }
{         call KK_set_conditions, after you've called this procedure.  }
BEGIN
     WITH KK_conditions DO
          BEGIN
          kk0 := 1.0;          { Noise conditions at auditorium of the }
          CC0 := 65.0;         { Toshiba Central Research Laboratory.  }
          k0 := 1.0;
          C0 := 65.0;
          p57 := 0.141589156;  { (microbar) 2*10^-1.15 at 57 dBspl.    }
          nh := 0.15;	       { Exponents for sound pressure correc-  }
          nl := 0.32;          { tions, according to K & K.            }
          ne := 0.20;
          beta := 0.25;        { Extended power law constant, K & K.   }
          Lm := 24.00;         { (dBspl) Level difference at which     }
                               { perfect masking occurs.               }
          Lfb := 57.00;        { Use fixed level at fb calculation.    }
        { For the preprocessor:                                        }
          match := 0.00001;    { (cents) Quite strict.                 }
          fl := 20;            { (Hertz) Auditive range from.          }
          fh := 20000;
          slope := 60;         { (dBspl/octave).                       }
          treshold := 18;      { (dBspl) No contribution below this.   }
          END;
          KK_set_conditions;   { Calculates derived parameters.        }
END; { of KK_set_default_conditions }

{------------------ PREPROCESSOR FOR DISSONANCE MODEL: ----------------}
PROCEDURE KK_preprocess (VAR Stim, Stim_sorted : KK_spectrum);
{         This procedure reads the raw spectral data from Stim and     }
{         outputs a sorted copy in Stim_sorted which then can be used  }
{         by KK_dissonance. Besides that frequency components will be- }
{         come ordered on frequency (low to high) and some components  }
{         may disappear. Dependant on the values in KK_conditions,     }
{         "equal" frequencies will be (phase-dependantly) added and    }
{         frequencies below fl or above fh will be attenuated. Lastly, }
{         all components below treshold are trimmed from the spectrum. }
  VAR p, diff : REAL;
        Point : ARRAY[1..KK_number_max] OF BYTE;
         i, j : INTEGER;

  PROCEDURE Sort_frequencies;
  {         Fills the array Point with sorted BYTE-indices to fre-     }
  {         quencies (from low to high).                               }
  VAR i, l, H : BYTE;
        j, a  : INTEGER;
  BEGIN
    FOR i := 1 TO Stim.number DO            { Create array of pointers }
        Point[i] := i - 1;                  { to frequency components. }
    a := Stim.number DIV 2;
    WHILE a > 0 DO
      BEGIN
      FOR i := a + 1 TO Stim.number DO
          BEGIN
          j := i - a;
          WHILE j > 0 DO
                BEGIN
                l := j + a;
                IF Stim.Hertz[Point[j]] <= Stim.Hertz[Point[l]] THEN
                   j := 0
                ELSE
                   BEGIN
                   H := Point[j];
                   Point[j] := Point[l];  Point[l] := H
                   END;
                j := j - a
                END
          END;
      a := a DIV 2
      END
  END;

  PROCEDURE Add_phase_vectors (VAR amp1, ph1, amp2, ph2 : REAL);
  {         Adds second phase vector (with linear magnitude amp2 and   }
  {         radian phase ph2) to the first vector. The result is thus  }
  {         written to amp1 and ph1. Note that this is only valid when }
  {         the two frequencies are exactly equal.                     }
  VAR i      : INTEGER;
      Re, Im : REAL;
  BEGIN
     Re := amp1 * COS(ph1) + amp2 * COS(ph2); { Convert polar form to  }
     Im := amp1 * SIN(ph1) + amp2 * SIN(ph2); { cartesian and add up.  }
     amp1 := SQRT (Re*Re + Im*Im);            { Resulting magnitude.   }
     IF Re = 0 THEN                           { Resulting phase.       }
        IF Im > 0 THEN
           ph1 := PI/2
        ELSE
           ph1 := -PI/2         { If Re = Im = 0 phase doesn't matter. }
     ELSE
        BEGIN
        ph1 := ARCTAN (Im/Re);                  { 1st or 4th quadrant. }
        IF Re < 0 THEN
           IF Im < 0 THEN
              ph1 := ph1 - PI                          { 3rd quadrant. }
           ELSE
              ph1 := ph1 + PI                          { 2nd quadrant. }
        END
  END;

  FUNCTION Bandpass (f : REAL) : REAL;
  {        Attenuates frequency components below .fl and above .fh     }
  {        (Hertz), with a slope of .slope (dBspl per octave).  To-    }
  {        gether with .treshold, this function is used for a gradual  }
  {        removal of components outside the audible range.            }
  BEGIN
    WITH KK_conditions DO
       IF (fl <= f) AND (f <= fh) THEN
          bandpass := 1
       ELSE
           IF f > fh THEN
              bandpass := POWER (fh/f, expon)
           ELSE {thus f < fl}
              bandpass := POWER (f/fl, expon)
  END;

BEGIN                              { Begin of procedure KK_preprocess. }
  Sort_frequencies;
  i := 1;   { Reading from pointer array, index 1 to Stim.number.      }
  j := 0;   { Writing to Stim_sorted, index 0 to Stim_sorted.number-1. }
  WITH Stim_sorted DO
    BEGIN
    REPEAT
          Hertz[j] := Stim.Hertz[Point[i]];    { Copy frequency compo- }
          ubar[j] := Stim.ubar[Point[i]];      { nent to Stim_sorted.  }
          rad[j] := Stim.rad[Point[i]];
          diff := KK_conditions.dev * Hertz[j];
          WHILE (i < Stim.number) AND
                (ABS (Stim.Hertz[Point[i+1]] - Hertz[j]) < diff) DO
             BEGIN                   { Add up "identical" frequencies. }
             i := i + 1;
             Add_phase_vectors (ubar[j], rad[j], Stim.ubar[Point[i]],
                                                 Stim.rad[Point[i]])
             END;
          p := ubar[j] * bandpass(Hertz[j]);    { Attenuated pressure. }
          IF p >= KK_conditions.pt THEN         { Skip when too weak.  }
             BEGIN
             ubar[j] := p;       { Possible sound pressure adjustment. }
             j := j + 1
             END;
          i := i + 1;
    UNTIL i > Stim.number;
    number := j;
    END;
END;


{------------------ THE KAMEOKA AND KURIYAGAWA MODEL: -----------------}
FUNCTION KK_fb (f1, L: REAL) : REAL;
{        Calculates the most dissonant frequency difference (fb in Hz) }
{        from lowest frequency (f1 in Hz) and the level (L in dB SPL). }
{        See J.A.S.A.: page 1455, equation 4 / page 1462, equation 6.  }
BEGIN
     IF L <= 17 THEN
        BEGIN
        KK_fb := 0;
        Message
('Error: Most dissonant frequency difference unknown below 17 dBspl.')
        END
     ELSE
        KK_fb := 0.05675 * (L-17) * POWER (f1, 0.477)
END;


FUNCTION KK_dissonance (VAR stim : KK_spectrum) : REAL;
{        Calculates total absolute dissonance of frequency spectrum    }
{        stored in stim. The function does NOT destruct stim. Frequen- }
{        cies in stim are assumed to be sorted and unique. So always:  }
{        stim.Hertz[a-1] < stim.Hertz[a]. This function skips all dy-  }
{        ads with over octave separation (they do not contribute to    }
{        total dissonance (independant from Co, Ko, Co' and ko').      }
{        Always uses the calculation conditions written in the global  }
{        variable KK_conditions. Furthermore, if, and only if, the     }
{        calc-Lfb option is true, the total intensity level will be    }
{        left in KK_conditions.Lfb.                                    }
  FUNCTION D2i (VAR f1, p1, f2, p2, fb : REAL): REAL;
  {        Calculates the real absolute dissonance of a dyad without   }
  {        internal and external noises. Modified for frequency sepa-  }
  {        rations smaller than 1%: D'2ei is then (k'o * C'o = 65), so }
  {        D2i always becomes 0 (like over octave sep.independant from }
  {        k'o, C'o, ko or Co). f1 and f2 are the lower and higher     }
  {        frequencies (Hertz). p1 and p2 are the sound pressures      }
  {        (microbars) of respectively the lower and higher frequen-   }
  {        cies. fb is the most dissonant frequency difference (Hertz).}
  {        Over octave separation never occurs here anymore. Perfect   }
  {        masking occurs when the level difference exceeds Lm         }
  {        (decibels). The pressure ratio is then greater than mask_r  }
  {        and D2i becomes 0.                                          }
  VAR denom, numer, DD2ei, fD2i, p1_p2 : REAL;
  BEGIN
  WITH KK_conditions DO
    BEGIN                               { Masking or small separation. }
    p1_p2 := p1 / p2;                   { ATTENTION: p2 must be > 0.   }
    IF (p1_p2 > mask_r) OR (p1_p2 < mask_r_inv) OR (f2/f1 < 1.01) THEN                           { SMALL SEPARATION }
       D2i := 0
    ELSE
       BEGIN                              { Sound pressure correction. }
       IF p1_p2 >= 1 THEN
          fD2i := POWER (p1,ne_nh) * POWER (p2,nh) * p57_ne
       ELSE
          fD2i := POWER (p2,ne_nl) * POWER (p1,nl) * p57_ne;
       denom := LOG (fb/f1);
       numer := LOG ((f2-f1)/f1);
       IF f2 - f1 <= fb THEN                         { Dynamic domain. }
          DD2ei := kk0*(CC0 + 100*(2+numer)/(2+denom))
       ELSE                                           { Static domain. }
          DD2ei := kk0*(CC0 + 10 + 90*numer/denom);
       D2i := fD2i*kk0*POWER (POWER (DD2ei/kk0, beta_inv)-DDIn, beta);
       END
    END
  END;

VAR  i, j                : INTEGER;
     octave, accu, L, fb : REAL;
BEGIN { of function KK_dissonance }
  WITH Stim, KK_conditions DO
     BEGIN
     IF number > KK_number_max THEN Message
 ('Error: Too many frequency components.');
     accu := Dn0_beta_inv; { Background dissonance = (k0*c0)^(1/beta). }
     IF Lfb <> -1 THEN
        L := Lfb               { Use specified fixed level at fb calc. }
     ELSE
        BEGIN
        L := 0;                { Calculate average dB level.           }
        FOR i := 0 TO number-1 DO
            L := L + ubar_to_dBspl (ubar[i]);
        L := L / number;
        END;
     FOR i := 0 TO number - 2 DO         { All possible dyads within   }
         BEGIN                           { critical band (1 octave).   }
         IF Hertz[i] >= Hertz[i+1] THEN  { Check frequency rank order. }
            Message
 ('Error: Frequencies in wrong order, preprocessing recommended.');
         octave := 2 * Hertz[i];
         fb := KK_fb (Hertz[i], L);
         j := i + 1;
         WHILE (Hertz[j] <= octave) AND (j < Number) DO
            BEGIN
            accu := accu +
                    POWER (D2i (Hertz[i],ubar[i],Hertz[j],ubar[j], fb),
                           beta_inv);             { Inverse power Law. }
            j := j + 1
            END
         END;
     KK_dissonance := k0_kk0 * POWER (accu, beta)         { Power law. }
     END
END; { of function KK_dissonance }


{-------------------------- BASIC USER I/O: ---------------------------}
PROCEDURE KK_show_conditions;
BEGIN
  WITH KK_conditions DO
       BEGIN
       WRITELN ('CONDITIONS:          nl =',nl:9:6,'     ne =',ne:9:6,
                '     nh =', nh:9:6);
       WRITELN ('  p57 =', P57:7:4, ' ubar','  ko =', k0:9:6,
           '    ko',CHR($27),' =',kk0:9:6,'     Lm =', Lm:9:4, ' dB');
       WRITE   ('    ',CHR($E1),' =', beta:10:7,'    Co =', C0:9:4,
                '    Co', CHR($27), ' =', CC0:9:4);
       IF Lfb <> -1 THEN
        WRITELN('    Lfb =', Lfb:9:4, ' dB')
       ELSE
        WRITELN('    Lfb = total average');
       WRITELN ('   fl =',fl:7:2,' Hz    fh =',fh:6:0,' Hz  ',
          'tresh =',treshold:6:2,' dB  match =', match:9:6, ' cents');
       WRITELN ('slope =',slope:7:2,' dB/8');
       WRITELN
       END
END;


PROCEDURE KK_show_stimulus (VAR S : KK_spectrum; format : BYTE);
VAR i, V, R, A : INTEGER;
BEGIN
  WITH S DO
       BEGIN
       V := 0;  R := Number;
       WHILE R>0 DO
         BEGIN
         IF R < 8 THEN
            A := R ELSE A := 8;
         IF (format AND 1)>0 THEN            { format 1 = Print Hertz. }
            BEGIN
            WRITE ('Hertz:');
            FOR i := V TO V+A-1 DO
                WRITE (Hertz[i]:9:2)
            END;
         IF (format AND 2)>0 THEN            { format 2 = Print dBspl. }
            BEGIN
            WRITELN; WRITE ('dBspl:');
            FOR i := V TO V+A-1 DO
                WRITE (ubar_to_dBspl (ubar[i]):9:2)
            END;
         IF (format AND 4)>0 THEN            { format 4 = Print ubar.  }
            BEGIN
            WRITELN; WRITE (' ubar:');
            FOR i := V TO V+A-1 DO
                WRITE (ubar[i]:9:5)
            END;
         IF (format AND 8)>0 THEN            { format 8 = Print angle. }
            BEGIN
            WRITELN; WRITE ('angle:');
            FOR i := V TO V+A-1 DO
                WRITE (rad[i]:9:4)
            END;
            WRITELN; WRITELN;
         R := R-A;  V := V+A;
         END;
       END
END;

PROCEDURE Message {S: STRING};
BEGIN
     IF S<>'' THEN
        WRITELN ('> FKK ', S);
     WRITE ('  Hit any key to continu. ');
     WHILE READKEY = '' DO
           WRITE;
     GOTOXY (1, WHEREY);
     WRITE ('                          ');
     GOTOXY (1, WHEREY);
END;

PROCEDURE KK_show_info;
CONST s = '        ';
BEGIN
  WRITELN;
  WRITELN(s,'ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿');
    WRITE(s,'³       Using unit FKK.TPU Version ',KK_version);
    GotoXY(69,WhereY); WRITELN('³');
  WRITELN(s,'³ Pascal implementation of the sensory dissonance model ac- ³');
  WRITELN(s,'³ cording to Akio Kameoka and Mamoru Kuriyagawa (published  ³');
  WRITELN(s,'³ in Journal of the Acoustical Society of America, 1969).   ³');
  WRITELN(s,'³                Pieter Suurmond (1993-95).                 ³');
  WRITELN(s,'ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ');
END;

{---------------------------- CHECKS: ---------------------------------}
PROCEDURE KK_checks;
{         This procedure provides a primitive user interface to the    }
{         dissonance model in which you may perform several checks.    }
{         It also contains some examples that show you how to use this }
{         Pascal-unit.                                                 }

VAR C: CHAR;
    Stimulus, Sorted : KK_SPECTRUM;    { Contains input for the model. }

PROCEDURE PRINT_MDFD;
{         Prints most dissonant frequency difference for several      }
{         lower frequencies.                                          }
VAR f1, L : REAL;
    OCT   : INTEGER;
BEGIN
     f1 := 110;       {Hz}
     L  := 57;        {dB SPL}
     WRITELN;
     WRITELN ('Most dissonant frequency difference:');
     WRITELN;
     WRITELN ('f1 [Hz]: level [dB SPL]: fb [Hz]:');
     WRITELN;
     FOR OCT :=1 TO 7 DO
         BEGIN
              WRITELN (f1:6:2, '  L=',L:6:2,'   -->  ', KK_fb(f1,L):6:2 );
              f1 := 2 * f1;
         END
END;


PROCEDURE KK_Expt_1;
  VAR i: INTEGER;
  PROCEDURE TAKE_KK_Expt_1 (stim_numb: INTEGER);
  {         Fills Stimulus with one of the stimuli (1 to 11) used at   }
  {         Expt.1 of Kameoka and Kuriyagawa (page 1464 in J.A.S.A.).  }
  CONST stim : ARRAY[1..11, 1..2, 0..5] OF REAL =
  {1}  (((440,  0,    0,    0,    0,    0),  { freq. in Hertz (0=end). }
         (60,   0,    0,    0,    0,    0)), { levels in dB SPL.       }
  {2}   ((440,  455,  484,  581,  0,    0),
         (57.5, 49.5, 44,   45,   0,    0)),
  {3}   ((440,  455,  484,  880,  0,    0),
         (56.5, 54,   53.5, 44.5, 0,    0)),
  {4}   ((440,  484,  0,    0,    0,    0),
         (57,   57.5, 0,    0,    0,    0)),
  {5}   ((440,  455,  484,  581,  880,  0),
         (55,   57,   54,   54.5, 46,   0)),
  {6}   ((440,  455,  484,  581,  880,  0),
         (50,   54,   58.5, 54.5, 50,   0)),
  {7}   ((440,  455,  484,  581,  880,  0),
         (46,   50.5, 55.5, 57,   54.5, 0)),
  {8}   ((440,  455,  484,  581,  880,  0),
         (42,   46,   50,   55,   58,   0)),
  {9}   ((440,  484,  581,  880,  0,    0),
         (51,   54,   50.5, 58,   0,    0)),
  {10}  ((440,  455,  484,  581,  880,  0),
         (54,   43.5, 58,   50.5, 58.5, 0)),
  {11}  ((440,  455,  484,  880,  0,    0),
         (57,   50.5, 57.5, 50.5, 0,    0)));
  BEGIN
  WITH Stimulus DO
       BEGIN
       number := 0;                             { Clear Stimulus. }
       WHILE stim[stim_numb, 1, number] <> 0 DO
             BEGIN
             Hertz[number] := stim[stim_numb, 1, number];
             ubar[number] := dBspl_to_ubar (stim[stim_numb,2,number]);
             rad[number] := 0;
             number := number + 1
             END
       END
  END;
BEGIN
     WRITELN;
     WRITELN ('Kameoka and Kuriyagawa experiment 1. ',
              'See J.A.S.A. page 1464.');
     WRITELN;
     KK_set_default_conditions;
     KK_show_conditions;
     FOR i := 1 TO 11 DO
         BEGIN
         WRITE ('        Stimulus ', i, ':  ');
         TAKE_KK_Expt_1 (i);
         WRITELN ('AD =', KK_dissonance(Stimulus):7:2);
         KK_show_stimulus (Stimulus, 3);    { 3 means Hertz and dBspl. }
         Message('');
         END
END;


PROCEDURE KK_Expt_2;
  VAR i: INTEGER;
  PROCEDURE TAKE_KK_Expt_2 (stim_numb: INTEGER);
  {         Fills Stimulus with one of the stimuli (1 to 14) used at   }
  {         Expt.2 of Kameoka and Kuriyagawa (page 1464 in JASA).      }
  CONST stim : ARRAY[1..14, 1..2, 0..12] OF REAL =
  {1}  (((440,  461,  484,  0,    0,    0,    0,    0,    0,    0,
          0,    0,    0),
         (56,   49.5, 42,   0,    0,    0,    0,    0,    0,    0,
          0,    0,    0)),
  {2}   ((440,  461,  484,  573,  880,  0,    0,    0,    0,    0,
          0,    0,    0),
         (55.5, 50.5, 48.5, 44.5, 42,   0,    0,    0,    0,    0,
          0,    0,    0)),
  {3}   ((440,  459,  484,  572,  880,  0,    0,    0,    0,    0,
          0,    0,    0),
         (56,   52,   49.5, 44.5, 42.5, 0,    0,    0,    0,    0,
          0,    0,    0)),
  {4}   ((440,  460,  482,  570,  880,  0,    0,    0,    0,    0,
          0,    0,    0),
         (55,   56,   52,   49.5, 46,   0,    0,    0,    0,    0,
          0,    0,    0)),
  {5}   ((440,  484,  880,  0,    0,    0,    0,    0,    0,    0,
          0,    0,    0),
         (57.5, 56.5, 50,   0,    0,    0,    0,    0,    0,    0,
          0,    0,    0)),
  {6}   ((440,  482,  880,  975,  1300, 1402, 1860, 0,    0,    0,
          0,    0,    0),
         (56,   56.5, 49.5, 55,   54.5, 49.5, 42,   0,    0,    0,
          0,    0,    0)),
  {7}   ((440,  497,  880,  934,  1306, 1450, 1716, 1890, 2135, 2372,
          0,    0,    0),
         (55,   55,   52.5, 49.5, 52.5, 51.5, 48.5, 44.5, 47,   42.5,
          0,    0,    0)),
  {8}   ((440,  497,  880,  924,  1305, 1452, 1718, 1886, 2131, 2347,
          2580, 2880, 0),
         (56,   56,   54.5, 54.5, 52,   51.5, 49.5, 49,   49,   49.5,
          45.5, 44,   0)),
  {9}   ((440,  496,  880,  912,  1305, 1334, 1698, 1803, 2108, 2180,
          2543, 2642, 0),
         (56.5, 56,   49,   49.5, 51,   51,   50,   50,   53,   53.5,
          44,   44,   0)),
  {10}  ((440,  496,  880,  912,  1296, 1337, 1698, 1822, 2100, 2200,
          2555, 2642, 0),
         (55.5, 55,   49.5, 50,   50.5, 50.5, 49.5, 50,   51.5, 51.5,
          42.5, 43,   0)),
  {11}  ((440,  704,  880,  1275, 1406, 1712, 2031, 2133, 2580, 2706,
          0,    0,    0),
         (56,   56,   55,   52,   55,   50,   52,   46,   44,   50,
          0,    0,    0)),
  {12}  ((440,  704,  880,  1275, 1406, 1712, 2031, 2133, 2580, 2706,
          3355, 3920, 0),
         (56,   56,   54.5, 52,   53.5, 49.5, 52,   46,   45,   50.5,
          47.5, 43,   0)),
  {13}  ((440,  880,  1321, 1760, 2153, 2620, 0,    0,    0,    0,
          0,    0,    0),
         (55.5, 55.5, 55,   55,   55.5, 55,   0,    0,    0,    0,
          0,    0,    0)),
  {14}  ((440,  880,  1321, 1760, 2153, 2620, 0,    0,    0,    0,
          0,    0,    0),
         (57.5, 57.5, 55.5, 51.5, 47,   45,   0,    0,    0,    0,
          0,    0,    0)));
  VAR i : INTEGER;
  BEGIN
    WITH Stimulus DO
         BEGIN
         number := 0;
         WHILE stim[stim_numb, 1, number] <> 0 DO
               BEGIN
               Hertz[number] := stim[stim_numb, 1, number];
               ubar[number] := dBspl_to_ubar (stim[stim_numb,2,number]);
               rad[number] := 0;
               number := number + 1;
               END
         END
  END;
BEGIN
     WRITELN;
     WRITELN ('Kameoka and Kuriyagawa experiment 2. ',
              'See J.A.S.A. page 1464.');
     WRITELN;
     KK_set_default_conditions;
     KK_show_conditions;
     FOR i := 1 TO 14 DO
         BEGIN
         WRITE ('        Stimulus no. ', i, ':  ');
         TAKE_KK_Expt_2 (i);
         WRITELN ('AD =', KK_dissonance(Stimulus):7:2);
         KK_show_stimulus(Stimulus, 3);     { 3 means Hertz and dBspl. }
         Message('');
         END
END;


PROCEDURE KK_Expt_3;
VAR r : REAL;
 PROCEDURE TAKE_KK_Expt_3 (ratio: REAL);
 {         Fills Stimulus with two complex harmonic tones. Fundamental }
 {         frequency of the first is fixed at 440 Hz, the second de-   }
 {         pends on ratio. Spectral structures accord to experiment 3  }
 {         of Kameoka and Kuriyagawa. All phases are assumed to be 0.  }
 CONST levels : ARRAY[1..8] OF REAL =       { Levels of the eight      }
       (-12, 0, 0, -4, -8, -12, -14, -16);  { harmonics (0=57 dB SPL). }
 VAR   i, j   : INTEGER;
 BEGIN
   WITH Stimulus DO
      BEGIN
      j := 0;
      number := 16;
      FOR i := 1 TO 8 DO
          BEGIN
          Hertz[j] := i * 440;                        { Fixed tone.    }
          ubar[j] := dBspl_to_ubar (57 + levels[i]);
          rad[j] := 0;
          j := j + 1;
          Hertz[j] := Hertz[j-1] * ratio;             { Variable tone. }
          ubar[j] := ubar[j-1];
          rad[j] := 0;
          j := j + 1
          END
      END
 END;
BEGIN
     WRITELN ('  K & K expt.3. See fig.7 on page 1465 in J.A.S.A..');
     KK_set_default_conditions; { match := 0.01;  Strict. }
     WRITE ('  Input parameter match (in cents) ');
     READLN (KK_conditions.match);
     KK_set_conditions;
     KK_show_conditions;
     WRITELN;
     WRITE ('> Input frequency ratio ');
     READLN (r);
     TAKE_KK_Expt_3 (r);
     KK_preprocess (Stimulus, Sorted);
     WRITELN ('  AD =', KK_dissonance (Sorted):7:2);
     KK_show_stimulus (Sorted, 11);  { 11 means Hertz + dBspl + angle. }
END;


PROCEDURE KK_Fig_9;
VAR  r : REAL;
    sp : INTEGER;
 PROCEDURE TAKE_KK_Fig_9 (spec : INTEGER; ratio : REAL);
 {         Fills Stimulus with two complex (harmonic) tones. With spec }
 {         you may choose the spectrum (7, 8 or 9) as given at fig. 9  }
 {         on page 1466 in J.A.S.A.. All phases are assumed to be 0.   }
 CONST freqs : ARRAY[7..9, 1..4] OF REAL =            { Frequencies of }
              ((440,  880,  1760, 2640),              { harmonics.     }
               (440,  1320, 2200, 3080),
               (440,  880,  1760, 3520));
 VAR i, j : INTEGER;
 BEGIN
  WITH Stimulus, KK_conditions DO
     BEGIN
     j := 0;
     number := 8;
     FOR i := 1 TO 4 DO
         BEGIN
         Hertz[j] := freqs[spec,i];               { Fixed tone. }
         ubar[j] := p57;
         rad[j] := 0;
         j := j + 1;
         Hertz[j] := ratio*freqs[spec,i];        { Variable tone. }
         ubar[j] := p57;
         rad[j] := 0;
         j := j + 1;
         END
     END;
 END;
BEGIN
     WRITELN ('  K & K fig. 9 in J.A.S.A..');
     KK_set_default_conditions; { match := 0.01;  Strict. }
     WRITE ('  Input parameter frequency match (in cents) ');
     READLN (KK_conditions.match);
     KK_set_conditions;
     KK_show_conditions;
     WRITELN;
     WRITE ('> Input frequency ratio ');
     READLN (r);
     FOR sp := 7 TO 9 DO
         BEGIN
         TAKE_KK_Fig_9 (sp, r);
         KK_preprocess (Stimulus, Sorted);
         WRITE ('Spectrum ', sp);
         WRITELN ('  AD =', KK_dissonance (Sorted):7:2);
         KK_show_stimulus (Sorted, 11);  { 11 = Hertz + dBspl + angle. }
         Message ('')
         END
END;


CONST s = '--------------------------------------';
BEGIN                                                     { KK_checks. }
     KK_set_default_conditions;
     C := '0';
     WRITELN (' ',s,s);
     REPEAT
           WRITELN ('MENU (CHECKING THE FKK UNIT):');
           WRITELN ('M = Most dissonant freq. diff.       ',
             '1 = Kameoka and Kuriyagawa expt 1.');
           WRITELN ('2 = Kameoka and Kuriyagawa expt 2.   ',
             '3 = Kameoka and Kuriyagawa expt 3.');
           WRITELN ('9 = Kameoka and Kuriyagawa fig 9.    ',
             'Q = Quit FKK-checks.');
           REPEAT
                 C := READKEY;
                 IF (CHR($60) < C) AND (C < CHR($7B)) THEN  { Up case. }
                    C := CHR(ORD(C) - $20);
           UNTIL C IN ['M','1','2','3','9','Q'];
           CASE C OF
                'M': PRINT_MDFD;
                '1': KK_Expt_1;
                '2': KK_Expt_2;
                '3': KK_Expt_3;
                '9': KK_Fig_9
           END;{Case}
           WRITELN;
     UNTIL C = 'Q';
     WRITELN (' ',s,s)
END;

{--------------- INITIALIZATION SECTION OF THE FKK UNIT: --------------}
BEGIN
     KK_show_info;                                        { Only once. }
     KK_set_default_conditions;
END.
{----------------------------------------------------------------------}