Original Source for <tt>resample</tt> up JOS Home

Original source code for resample

Below is the original source listing for resample. It is written in the SAIL programming language, with time-critical inner loops written in FAIL (the assembly language used for the PDP KL-10 at the Stanford AI Lab at the time).

This source code is released under the BSD License

Note that SAIL's left-arrow has been replaced by underbar (_) below.


BEGIN "SRC"     
COMMENT
        Implement sampling rate conversions by (almost) arbitrary factors.
        Based on SRCONV.SAI[SYS,MUS] with new algorithm by JOS&PG.
        The program internally uses 16-bit data and 16-bit filter
        coefficients.

        Reference: "A Flexible Sampling-Rate Conversion Algorithm,"
        J. O. Smith and P. Gossett, ICASSP, San Diego, 1984.

        To do: Fast 16-bit IO not yet implemented (buggy routines
        have been written but are not enabled). Also, need to add
        stereo case. Fast 16-bit stereo IO routines by MMM are here
        but not yet used.

        * Need overflow detection in fail section. Also want to saturate
          instead of wrap-around and report number of clipped samples.

Modification history:

        8/27/83 - JOS - Changed filter file format (added Nwing).
        8/29/83 - JOS - Removed 0.95 guard factor in filter normalization.
        8/30/83 - JOS - Changed width of scale factor (NlpScl) from 8 to 17.
                        Moved conversion constants to SRC.PRM[SYS,MUS].
        9/ 3/83 - JOS - Changed ASH to LSH in filter IO to get 1 more MSB.
        2/11/85 - JOS - Absorbed REQUIRE file "SRC.PRM" (page 10). Only
                        JOS's XF software uses it besides SRC (as far as I know).
        5/ 2/85 - JOS - Changed Xoff from ((Nmult-1)/2)*... to ((Nmult+1)/2)*....
;
REQUIRE "{}<>" DELIMITERS;
DEFINE #="comment",CrLf="'15&'12",tab={""&'11},Sam16="4",Cr="'15",
        TwoTracks={(2*31*128)},
        thru={step 1 until};
DEFINE Saf = {};

REQUIRE "SYS:DEFAUL.HDR" SOURCE!FILE;

REQUIRE "JOSLIB.REQ[LIB,JOS]" SOURCE!FILE;

REQUIRE "JAMLIB.REL[SUB,SYS]" LIBRARY;
REQUIRE "RHEAD.REL[SYS,MUS]" LOAD!MODULE;
REQUIRE "WHEAD.REL[SYS,MUS]" LOAD!MODULE;

EXTERNAL BOOLEAN PROCEDURE SANDI(
        INTEGER Chan, StSamp, Nsamps;
        REFERENCE REAL ARRAY X;
        INTEGER FilSamps, Headed, FilPak, Xpack(-1));

EXTERNAL BOOLEAN PROCEDURE SANDO(
        INTEGER Chan, StSamp, Nsamps;
        REFERENCE REAL ARRAY X;
        REFERENCE INTEGER ARRAY Hist;
        REFERENCE INTEGER FilSamps;
        INTEGER Headed, FilPak, Xpack(-1));

EXTERNAL PROCEDURE FIRLPF(REAL ARRAY Imp; INTEGER N; REAL Cutoff,Clock,Beta);

EXTERNAL STRING PROCEDURE DEFAULT(STRING s; REFERENCE STRING Dev;
        STRING Defext,Defdev);
EXTERNAL PROCEDURE TRPINI(INTEGER Flag);

INTEGER PROCEDURE Pwr2(INTEGER N);
BEGIN "Pwr2"
  INTEGER Nfft;
  Nfft _ LOG(N)/LOG(2) - 0.000001;
  Nfft _ 2^(Nfft+1); 
  RETURN(Nfft);
END "Pwr2";

# Display lowpass filter;

INTERNAL INTEGER TRACE;

PROCEDURE CkFIR(INTEGER ARRAY Imp; INTEGER Nwing; REAL Dh; BOOLEAN Interp);
COMMENT Display frequency response of interpolation lowpass;
BEGIN "CkFIR"
# REQUIRE "JOSLIB.REQ[LIB,JOS]" SOURCE!FILE;
    EXTERNAL PROCEDURE FFT(INTEGER Nfft; REAL ARRAY X,A,B);
    EXTERNAL PROCEDURE ShowLogMag(INTEGER Nfft; REAL ARRAY A,B; STRING Title);
    REDEFINE NoSet="(1.7014118@38)";
    EXTERNAL INTEGER PROCEDURE DPYED (REAL ARRAY Data; INTEGER Npoints(-1);
        STRING Xlabel(NULL),Ylabel(NULL); REAL Vxmin(NoSet),Vxmax(NoSet),
        Miny(NoSet),Maxy(NoSet));

  PROCEDURE FFTwing(INTEGER Nwing,N; REAL Saf ARRAY X);
  COMMENT Given right wing of FIR filter in X[1:Nwing], create
          left wing at right of X[1:N] suitable for FFT (for
          frequency response);
  BEGIN "FFTwing"
    INTEGER i;
    REAL Saf ARRAY Temp[1:Nwing];
    ARRTRAN(Temp,X);                                  # Temp _ right wing;
    FOR i_1 Thru Nwing-1 DO X[N-i+1]_X[i+1];          # Far right _ left wing;
    FOR i_1 Thru Nwing DO X[i]_Temp[i];               # Far left _ right wing;
    FOR i_Nwing+1 Thru N-Nwing+1 DO X[i]_0;           # Zero middle;
  END "FFTwing";

  INTEGER Nfft,Nspec;
  Nfft _ Pwr2(2*Nwing/Dh)*4;
  Nspec _ (Nfft DIV 2) + 1;
  BEGIN
    INTEGER k,Iho;
    REAL Ho;
    REAL ARRAY X[1:Nfft],A,B[1:Nspec];
    k_0;
    IF Interp THEN
      FOR Ho_0 STEP Dh UNTIL Nwing-1 DO 
      BEGIN 
        Iho_Ho; 
        X[k_k+1]_Imp[Iho]+(Ho-Iho)*(Imp[Iho+1 MIN Nwing-1]-Imp[Iho]);
      END
    ELSE
      FOR Ho_0 STEP Dh UNTIL Nwing-1 DO X[k_k+1]_Imp[Ho];
    DpyEd(X,k,"Right wing of effective impulse response subsampled by "&CVF(Dh));
    FFTwing(k,Nfft,X);
    FFT(Nfft,X,A,B);
    ShowLogMag(Nfft,A,B,"Frequency response "&(IF Interp THEN "WITH" ELSE
                        "WITHOUT")&" interpolation");
  END;
END "CkFIR";

# Two-channel 16-bit IO (by MMM)                (not yet installed)

These take the place of SANDI and SANDO.  The use of buffered output
without any USETOs allows writing to the tape drive, and the reading of
samples and splitting into 2 arrays is more efficiently accomplished.
;

PROCEDURE I16B2C (INTEGER Chan, StartSamp, Nsamps; Saf INTEGER ARRAY Chan1, Chan2);
    BEGIN "I16B2C"
    INTEGER I, J, V1, V2, Rec; 
    Saf INTEGER ARRAY InArr [1:Nsamps];
    StartSamp _ StartSamp + 128;
    Rec _ (StartSamp / 128);
    USETI (Chan, Rec+1);
    J _ StartSamp - (Rec*128);
    ARRYIN (Chan, InArr[1], J);
    ARRYIN (Chan, InArr[1], Nsamps);
    FOR I _ 1 STEP 1 UNTIL Nsamps DO BEGIN
        J _ InArr[I];
        Chan1[I] _ IF (J LAND '20000000000) THEN
            (J LSH -16) LOR '777777600000
          ELSE 
            (J LSH -16) LAND '177777;
        Chan2[I] _ IF (J LAND '100000) THEN
            J LOR '777777600000
          ELSE 
            J LAND '177777;
        END;
    END "I16B2C";

  PROCEDURE W16B2C (INTEGER Chan, Nsamps; Saf INTEGER ARRAY Chan1, Chan2);
  BEGIN "W16B2C"
    INTEGER I, A1, A2;
    Saf INTEGER ARRAY OutArr [1:Nsamps];
    FOR I _ 1 STEP 1 UNTIL Nsamps DO
        OutArr[I] _ (Chan1[I] LSH 16) LOR (Chan2[I] LAND '177777);
    ARRYOUT(Chan, OutArr[1], Nsamps);
  END "W16B2C";

# Single channel 16-bit IO.

I16B1C seems to work only on the first call to it.
W16B1C has to be rewritten because it only writes multiples of 128 words.
;

PROCEDURE I16B1C (INTEGER Chan, StartSamp, Nsamps; Saf INTEGER ARRAY Data);
BEGIN "I16B1C"
    INTEGER I, J, K, Rec,Ns,Ss,Nz;
    Saf INTEGER ARRAY InArr [1:(Nsamps DIV 2) + 1 + 128];
    Ns _ (Nsamps DIV 2) + 1 + 128; # Enough for partial record read;
    IF StartSamp<0 THEN BEGIN Nz_-StartSamp; StartSamp_0; END ELSE Nz _ 0;
    Ss _ StartSamp DIV 2;       # True word number containing first sample;
    Ss _ Ss + 128;              # Step over header;
    Rec _ (Ss DIV 128);         # First Record number, numbering from 0;
    USETI (Chan, Rec+1);        # System numbers from 1;
    J _ Ss - (Rec LSH 7);       # Ss - Rec*128 = No. leading words to ignore;
    ARRYIN (Chan, InArr[1], Ns);
    PRINT(" May need to save and restore AC's here",CrLf);

    QUICK!CODE "In16c1"
      DEFINE A="1",D="2",De="3",T="4";
      LABEL LH,RH,DONE;
        Move D,Data;            # Pointer to current Data sample;
        Add D,Nz;               # Number of initial zeros;
        Move De,Data;           # Pointer to end of Data + 1;
        Add  De,Nsamps;         # An absolute address;
        Move A,InArr;           # Pointer to current input array sample;
        Add A,J;                # Step over extra samples;
        Move T,StartSamp;       # 0 is first sample of file;
        Trne T,1;               # Odd?;
          Jrst RH;              # Yes, first word is in right half;
LH:     Caml D,De;              # Data filled?;
          Jrst DONE;            # Yes, return;
        Move T,@A;              # Get current input word;
        LSH T,4;                # Left justify it so sign bit is in bit 0;
        ASH T,-20;              # Right justify with sign extension;
        Movem T,@D;             # Deposit into Data array;
        Addi D,1;               # and advance output pointer;
RH:     Caml D,De;              # Data filled?;
          Jrst DONE;            # Yes, return;
        Move T,@A;              # Get current input word;
        LSH T,20;               # Left justify it so sign bit is in bit 0;
        ASH T,-20;              # Right justify with sign extension;
        Movem T,@D;             # Deposit into Data array;
        Addi D,1;               # Advance output pointer;
        Aoja A,LH;              # Advance input pointer;
DONE:   Jfcl;                   # Nothing left to do I hope;

    END "In16c1";

END "I16B1C";

PROCEDURE W16B1C (INTEGER Chan, Nsamps; Saf INTEGER ARRAY Data);
BEGIN "W16B1C"
    INTEGER I, J, Nout;
    OWN INTEGER Last;
    OWN BOOLEAN LastX;
    Saf INTEGER ARRAY OutArr [1:Nsamps];

    QUICK!CODE "O16c1"
      DEFINE A="1",D="2",De="3",T="4";
      LABEL LH,RH,LDone,Rdone,DONE;

        Move D,Data;            # Pointer to current Data sample;
        Move De,D;              # Pointer to end of Data + 1;
        Add  De,Nsamps;         # An absolute address;
        Move A,OutArr;          # Pointer to current output array sample;
        Move T,Nsamps;          # 0 is first sample of file;
        Skipn LastX;            # Is there a leftover from last call?;
          Jrst LH;              # No, first word goes to left half;
        Move T,Last;            # Yes, load leftover;
        Movem T,@A;             # put it in output;
        Jrst RH;                # and start loading at right half;
LH:     Caml D,De;              # Data emptied?;
          Jrst LDone;           # Yes, return;
        Move T,@D;              # Get current output word;
        LSH T,16;               # Left justify it in 32 rightmost bits;
        Movem T,@A;             # Deposit into Data array;
        Addi D,1;               # and advance output pointer;
RH:     Caml D,De;              # Data emptied?;
          Jrst RDone;           # Yes, return;
        Move T,@D;              # Get current output word;
        Andi T,'177777;         # Mask off possible sign bits;
        Iorm T,@A;              # Put it into output word;
        Addi D,1;               # Advance output pointer;
        Aoja A,LH;              # Advance input pointer;
LDone:  Setzm LastX;            # No leftovers;
        Jrst DONE;              # So bye bye;
RDone:  Setom LastX;            # Have a leftover;
        Move T,@A;              # Get it;
        Movem Last;             # and save for next call (CAN LOSE LAST SAMPLE);
        Subi A,1;               # Back up one;
DONE:   Sub A,OutArr;           # Number of output samples;
        Movem A,Nout;
    END "O16c1";

    ARRYOUT(Chan,OutArr[1],Nsamps);

END "W16B1C";

# Ridiculous conversion routines;

# These are necessary when input or output packing is not 16-bit.
  They should be eliminated when SANDI,SANDO support Xpack.
;

  PROCEDURE I16toR(INTEGER ARRAY Y; REAL ARRAY Yr; INTEGER N);
  # Convert 16-bit integer to Real;
  BEGIN
    INTEGER i;
    REAL Scl;
    Scl _ 2^-15;
    FOR i_0 Thru N-1 DO Yr[i] _ Y[i]*Scl;
    RETURN;
  END;

  INTEGER PROCEDURE RtoI16(REAL ARRAY Yr; INTEGER ARRAY Y; INTEGER N);
  # Convert Real to 16-bit integer;
  BEGIN
    INTEGER i;
    INTEGER Bust;
    REAL Scl;
    Scl _ 2^15-1;
    Bust _ 0;
    FOR i_0 Thru N-1 DO 
    BEGIN
      REAL Yri;
      Yri _ Yr[i];
      IF ABS(Yri)>1 THEN BEGIN Bust_Bust+1; Yri_Yri/ABS(Yri); END;
      Y[i] _ Yri*Scl;
    END;
    RETURN(Bust);
  END;

# Boolean, integer, and real input routines with help facility;

REDEFINE #="comment",thru=" step 1 until ",crlf="('15)&('12)",ALT="'175";
EXTERNAL INTEGER !skip!;

BOOLEAN PROCEDURE WantH(BOOLEAN Default; STRING Help,Prompt);
BEGIN
    STRING Ttystr;
    BOOLEAN Yes,No,Val;
    LABEL p;
p:  PRINT(Prompt,"?",(IF Help THEN " (Type ? for help)" ELSE NULL),":");
    Ttystr _ INCHRW;
    IF Ttystr="?" AND Help THEN BEGIN PRINT(CrLf,Help,CrLf); GO TO P; END;
    IF !skip!=ALT THEN PRINT('15&'12);     # ALT means no anywhere;
    IF Ttystr = CR THEN Ttystr _ INCHRW;   # Flush LF in CRLF;
    Yes _ ((Ttystr="Y") OR (Ttystr="y"));
    No  _ ((Ttystr="N") OR (Ttystr="n"));
    Val _ (Yes OR (Default AND NOT No));
    PRINT((IF VAL THEN " Yes" ELSE " No"),CrLf);
    RETURN(Val);
END;

BOOLEAN PROCEDURE RealInH(REFERENCE REAL X; STRING Help,Title);
BEGIN "RealInH"
    BOOLEAN Num;
    STRING t;
    INTEGER i;
    LABEL p;
p:  PRINT(Title," = ",X," _ ",(IF Help THEN "(Type ? for help) " ELSE NULL));
    !skip!_0;
    t_INCHWL;
    IF t="?" AND Help THEN BEGIN PRINT(Help,CrLf); GO TO P; END;
    IF !skip!=ALT THEN BEGIN PRINT(CRLF); RETURN(FALSE); END;
    Num_FALSE;
    FOR i_1 Thru LENGTH(t) DO 
        IF "0"  LEQ  t[i FOR 1]  LEQ  "9" THEN BEGIN Num_TRUE; DONE; END;
    IF Num THEN PRINT(Title," set to ",x_REALSCAN(t,0),CRLF) ELSE
    PRINT(Title, " not changed",CRLF);
    RETURN(TRUE);
END "RealInH";

BOOLEAN PROCEDURE IntInH(REFERENCE INTEGER X; STRING Help,Title);
BEGIN "IntInH"
    BOOLEAN Num;
    STRING t;
    INTEGER i;
    LABEL p;
p:  PRINT(Title," = ",X," _ ",(IF Help THEN "(Type ? for help) " ELSE NULL));
    !skip!_0;
    t_INCHWL;
    IF t="?" AND Help THEN BEGIN PRINT(Help,CrLf); GO TO P; END;
    IF !skip!=ALT THEN BEGIN PRINT(CRLF); RETURN(FALSE); END;
    Num_FALSE;
    FOR i_1 Thru LENGTH(t) DO 
        IF "0"  LEQ  t[i FOR 1]  LEQ  "9" THEN BEGIN Num_TRUE; DONE; END;
    IF Num THEN PRINT(Title," set to ",x_INTSCAN(t,0),CRLF) ELSE
    PRINT(Title, " not changed",CRLF);
    RETURN(TRUE);
END "IntInH";


# Declarations;

INTEGER ARRAY OH[0:128];
REAL ARRAY Head[1:128];
INTEGER Ny,Nx,LpScl,Nin,Xoff,Brk,Tlen,Nmult,Nwing,OutPack;
REAL Maxamp,Tmp,Factor,Froll,Beta,RtScl;
BOOLEAN FAIL,Eof,NewFilter,InterpFilt,FastInput,FastOutput;
STRING s,Ifile,Idev,Ofile,Odev;
STRING FactorHelp,NmultHelp,FrollHelp,BetaHelp,InterpFiltHelp;
LABEL P1,P2,P3,P4;

INTERNAL RECORD!CLASS File
    (
    INTEGER Channel, #chans, Pack, Spw, #samps, #elements, #ticks,
            Flag, State,
            Type;       #  0 = sound file, 1 = fft file                 ;
    REAL    Clock,
            Maxamp,     #  n dB down if fft file                        ;
            Bthresh,    #  Begin of segment threshold                   ;
            Ethresh,    #  End of segment threshold                     ;
            SegDur,     #  Minimum segment duration                     ;
            GapDur;     #  Minimum duration between segments            ;
    STRING  Dev, Name, Mode, Packstr, Text
    );

RECORD!POINTER (File) InpFptr, OutFptr;
DEFINE InpF(x) = {File:x[InpFptr]}, OutF(x) = {File:x[OutFptr]};

EXTERNAL PROCEDURE Rhead
    (
    RECORD!POINTER (File) Rp;
    REFERENCE REAL ARRAY Hdbuf;
    REFERENCE INTEGER FAIL;
    BOOLEAN Silence (FALSE)
    );
    #   Rhead (rp, hdbuf, Fail, true if don't want printout);

EXTERNAL PROCEDURE Whead
    (
    REFERENCE REAL ARRAY Head;
    REAL Clock;
    INTEGER Pack, #chans, #ticks;
    REAL Maxamp;
    STRING Cstr
    );
    #   Whead (head, clock, pack, #chans, #ticks, maxamp, cstr);

# Startup;

PRINT(CrLf,"Sampling rate conversion program ",
      COMPILER!BANNER[LENGTH(SCANC(COMPILER!BANNER,Tab,"","sinz"))+11 FOR 17],CrLf);

Trpini('14);    COMMENT Enable overflow interrupts;
SETFORMAT(0,6);

InpFptr _ NEW!RECORD (File);
OutFptr _ NEW!RECORD (File);

OutPack _ Sam16; # Samson box 16-bit format;

P1:DO
BEGIN
  OUTSTR("For mono files (R EXTRAC to split out a channel)."&Crlf&
         "Input "&Def_snd_in_ext&" file:  ");
  Ifile_Default(INCHWL,Idev,Def_snd_in_ext,Def_snd_in_dev);
  OUTSTR(Tab&"Reading file "&Idev&":"&Ifile&Crlf);
  OPEN(InpF(Channel)_GETCHAN,Idev,'17,0,0,200,Brk,Eof);
  LOOKUP(InpF(Channel),Ifile,FAIL);
  IF FAIL THEN PRINT("File not found",CrLf);
END
UNTIL NOT FAIL;

Rhead(InpFptr,Head,FAIL,TRACE); # Read and print header;
Tlen_InpF(#samps);

OUTSTR(Crlf&"Output "&Def_snd_out_ext&" file:   ");
Ofile_Default(INCHWL,Odev,Def_snd_out_ext,Def_snd_out_dev);
OUTSTR(Tab&"Writing file "&Odev&":"&Ofile&Crlf);
OPEN(OutF(Channel)_GETCHAN,Odev,'17,0,0,200,Brk,Eof);
ENTER(OutF(Channel),Ofile,FAIL);
IF FAIL THEN USERERR(0,0,"Can't ENTER output file");

FactorHelp _ 
"
        Factor is the amount by which the sampling rate is changed.
        If the sampling rate of the input signal is Srate1, then the
        sampling rate of the output is Factor*Srate1.
";

Factor _ 44100/InpF(Clock); # Default to SONY F1 rate;

P2: IF NOT RealInH(Factor,FactorHelp,"Sampling-rate conversion factor")
       THEN GO TO P1;

Maxamp_0;
s _ "From "&Idev&":"&Ifile&Crlf&
    "Comment = "&InpF(Text)&Crlf&
    "Sampling rate conversion by factor of "&CVF(Factor)&Crlf&
    "NOT FINISHED YET"&Crlf&Crlf;

OH[0]_0;
OutF(Dev   ) _ Odev;
OutF(Name  ) _ Ofile;
OutF(Clock ) _ InpF(Clock)*Factor;
OutF(#chans) _ InpF(#chans);             # better be 1 or 2;
OutF(#ticks) _ InpF(#ticks)/Factor+0.5;  # Unnecessary field - grr;
OutF(#samps) _ 0;
OutF(Text  ) _ s;
P3: IF NOT IntInH(OutF(Pack)_OutPack,"
        Packing codes are (0=12b, 1=18b, 3=FP, 4=16b SAM, 5=20b SAM)
        ","Output packing code")
    THEN GO TO P2;

#   Write current version of header (in head) in output file.           ;
Whead(Head,OutF(Clock),OutF(Pack),OutF(#chans),OutF(#ticks),0,OutF(Text));
USETO(OutF(Channel),1);
ARRYOUT(OutF(Channel),Head[1],128);

# IO buffer sizes;
Nx_TwoTracks;         # Two disk tracks;
Ny_Nx*Factor+0.5+1;   # Round and add 1 for safety;

CALL (InpF(Channel), "SHOWIT"); # Simulate an 2x for the input channel;

# FastInput _ (InpF(Pack)=Sam16);
# FastOutput _ (OutF(Pack)=Sam16);

# IF NOT FastInput THEN 
  PRINT(" This program runs faster on 16-bit data",CrLf); # Thanks to SANDIO;

FastInput _ FastOutput _ FALSE; # Buggy;

# Conversion constants;

# Old: REQUIRE "SRC.PRM" SOURCE!FILE; # Set converter parameters;

# Conversion constants read by SRC.SAI[SYS,MUS], INTRP.SAI[XF,JOS] et al;

#      ************************************************************
        Do not make the macro constants on this page into variables
        because they are needed at compile time to generate the 
        QUICK!CODE in sampling rate conversion subroutines.
       ************************************************************
;

DEFINE  
        Nhc     = 9,
        Na      = 8,
        Nh      = 16,
        Nb      = 16,
        Nhxn    = 14,
        NLpScl  = 17,
        Nhg     = Nh-Nhxn,
        Np      = Nhc+Na,
        Npc     = 2^Nhc,
        Pmask   = 2^Np-1,
        Amask   = 2^Na-1;


#  Npc  
        is the number of look-up values available for the
        lowpass filter between the beginning of its impulse
        response and the "cutoff time" of the filter. The cutoff
        time is defined as the reciprocal of the lowpass-filter
        cutoff frequency in Hz. For example, if the lowpass filter
        were a sinc function, Npc would be the index of the 
        impulse-response lookup-table corresponding to the 
        first zero-crossing of the sinc function. (The inverse
        first zero-crossing time of a sinc function equals its
        nominal cutoff frequency in Hz.) Npc must be a power of
        2 due to the details of the current implementation. The 
        default value of 512 is sufficiently high that using
        linear interpolation to fill in between the table entries
        gives approximately 16-bit accuracy in filter coefficients.

  Nhc   is log base 2 of Npc.

  Na    is the number of bits devoted to linear interpolation of the
        filter coefficients.

  Np    is Na + Nhc, the number of bits to the right of the binary point
        in the integer "time" variable. To the left of the point, it
        indexes the input array (X), and to the right, it is interpreted
        as a number between 0 and 1 sample of the input X. 
        Np must be less than 18 in this implementation.

  Nh    is the number of bits in the filter coefficients. The sum
        of Nh and the number of bits in the input data (typically 16)
        cannot exceed 36. Thus Nh should be less than 20 (20 does not work).
        The largest filter coefficient should nearly fill 16 bits (32767).

  Nb    is the number of bits in the input data. The sum of Nb and
        Nh cannot exceed 36.

  Nhxn  is the number of bits to right shift after multiplying each
        input sample times a filter coefficient. It can be as great as
        Nh and as small as 0. Nhxn = Nh-2 gives 2 guard bits in the 
        multiply-add accumulation. If Nhxn=0, the accumulation will soon
        overflow 36 bits.

  Nhg   is the number of guard bits in mpy-add accumulation ( = Nh-Nhxn).

 NLpScl is the number of bits allocated to the unity-gain normalization factor.
        The output of the lowpass filter is multiplied by LpScl and then
        right-shifted NLpScl bits. To avoid overflow, we must have 
        Nb+Nhg+NlpScl < 36.
;

  IFC NOT (Np<18) THENC 
        REQUIRE  " *** FATAL *** Np<18 DOES NOT HOLD" MESSAGE; ENDC
# IF NOT Np<18 THEN USERERR(0,0,
        " Page 10 of SRC.SAI[SYS,MUS] has violated Np<18");

  IFC NOT (Nb+Nhg+NlpScl<36) THENC 
        REQUIRE  " *** FATAL *** Nb+Nhg+NlpScl<36 DOES NOT HOLD" MESSAGE; ENDC
# IF NOT Nb+Nhg+NlpScl<36 THEN USERERR(0,0,
        " Page 10 of SRC.SAI[SYS,MUS] has violated Nb+Nhg+NlpScl<36");

  IFC NOT (Nh+Nb<36) THENC 
        REQUIRE  " *** FATAL *** Nh+Nb<36 DOES NOT HOLD" MESSAGE; ENDC
# IF NOT Nh+Nb<36 THEN USERERR(0,0,
        " Page 10 of SRC.SAI[SYS,MUS] has violated Nh+Nb<36");


# Set up conversion parameters;

NmultHelp _ 
"
        Nmult is the length of the symmetric FIR lowpass filter used
        by the sampling rate converter. It must be odd.
        This is the number of multiplies per output sample for 
        up-conversions (Factor>1), and is the number of multiplies 
        per input sample for down-conversions (Factor<1). Thus if
        the rate conversion is Srate2 _ Factor*Srate1, then you have
        Nmult*Srate1*(Factor MAX 1) multiplies per second of real time.
        Naturally, higher Nmult gives better lowpass-filtering at the
        expense of longer compute times. Nmult should be odd because
        it is the length of a symmetric FIR filter, and the current
        implementation requires a coefficient at the time origin.
";      

    Nmult _ 13;

FrollHelp _ 
"
        Froll determines the frequency at which the lowpass filter begins to
        roll-off. If Froll=1, then there is no ""guard zone"" and the filter
        roll-off region will be aliased. If Froll=0.85, for example, then
        the filter begins rolling off at 0.85*Srate/2, so that by Srate/2,
        the filter is well down and aliasing is reduced.  Since aliasing
        distortion is worse by far than loss of the high-frequency spectral
        amplitude, Froll<1 is highly recommended. The default of .85
        sacrifices the upper 15% of the spectrum as an anti-aliasing guard
        zone.
";

    Froll _ 0.85;

BetaHelp _ 
"
        Beta trades the rejection of the lowpass filter against the
        transition width from passband to stopband. Larger Beta means
        a slower transition and greater stopband rejection. See Rabiner
        and Gold (Th. and App. of DSP) under Kaiser windows for more about 
        Beta. The following table from Rabiner and Gold gives some feel 
        for the effect of Beta:

  All ripples in dB, width of transition band = D*N where N = window length

                    BETA    D       PB RIP  SB RIP
                    2.120   1.50    +-0.27  -30
                    3.384   2.23    .0864   -40
                    4.538   2.93    .0274   -50
                    5.658   3.62    .00868  -60
                    6.764   4.32    .00275  -70
                    7.865   5.0     .000868 -80
                    8.960   5.7     .000275 -90
                    10.056  6.4     .000087 -100
";

    Beta _ 5.7;

InterpFiltHelp _ 
"
        By electing to interpolate the filter look-up table,
        execution is slowed down but the quality of filtering
        is higher. Interpolation results in twice as many 
        multiply-adds per sample of output.
";

    InterpFilt _ TRUE;
    InterpFilt _ WantH(InterpFilt,InterpFiltHelp,"Interpolate filter");

    IF (NewFilter _ WantH(NULL,NULL,"Design new filter")) THEN
    WHILE TRUE DO
    BEGIN "ol"
      REAL t;
      DO IF NOT IntInH(Nmult,NmultHelp," (Odd) Filter length `Nmult' ") 
        THEN GO TO P3
        UNTIL Nmult MOD 2 = 1 AND Nmult>0;
      IF NOT RealInH(Froll,FrollHelp,"Normalized roll-off frequency (0:1) ")
        THEN CONTINUE "ol";
      IF NOT (01;
          Andi a,Amask; # Interpolation factor between IR samples;
Floop:  Caml Hp,He;     # Check for end of filter;
          Popj p,;      #   Done;
        Move t,@Hp;     # Put coeff into t;
        Jumpe IF,NoFi;  # May skip filter interpolation;
          Move 0,@Hdp;  # and load difference into AC0;
          Imul 0,a;     # Multiply by interpolation factor;
          ASH 0,-Na;    # a is logically between 0 and 1;
          Add t,0;      # t is now interpolated filter coefficient;
          Addi Hdp,Npc; # For next time;
NoFi:   Imul t,@Xp;     # Multiply coeff by the input sample;
        ASH t,-Nhxn;    # Leave some guard bits but come back some;
        Add v,t;        # Filter output;
        Addi Hp,Npc;    # IR step;
        Add Xp,Inc;     # Input signal step. NO CHECK MADE FOR ARRAY BOUNDS;
        Jrst Floop;

DONE:   Sub Yp,Y;
        Movem Yp,1;     # Return number of output samples;
        Move t,Regs;    # Restore AC's 2 and up;
        Addi t,2;
        Hrlz 0,t;
        Addi 0,2;
        Blt 0,'17;
END "srcu";

END "SrcUp";
# Sampling rate conversion subroutine;

  INTEGER PROCEDURE SrcUD(INTEGER ARRAY X,Y;
    REAL Factor;
    REFERENCE INTEGER Time;
    INTEGER Nx;
    INTEGER Nwing,LpScl; INTEGER ARRAY Imp,ImpD;
    BOOLEAN Interp(TRUE));

  BEGIN "SrcUD"
    REAL Dh;      # Step through filter impulse response;
    REAL Dt;      # Step through input signal;
    INTEGER EndTime; # When Time reaches EndTime, return to user;
    INTEGER Dhb,Dtb; # Fixed-point versions of Dh,Dt;

    Dt _ 1/Factor;        # Output sampling period;
    Dtb _ Dt*2^Np+0.5;    # Fixed-point representation;

    Dh _ Factor*Npc MIN Npc;  # Filter sampling period;
    Dhb _ Dh*2^Na+0.5;        # Fixed-point representation;

    EndTime _ Time + Nx*2^Np;

# FAIL coded segment;

QUICK!CODE "srcf"
  LABEL Lwing,Rwing,DONE,Floop,Shift,NoFi,Filter;
  INTEGER XpS;
  INTEGER ARRAY Regs[0:'17];

  DEFINE Xp=1,Yp=2,a=3,Ph=4,v=5,t=6,Xl=7,
         Hp='10,Hdp='11,He='13,IF='14,Inc='15,Ho='16,P='17;

        Hrrz 1,Regs;    # Save AC's;
        Move t,Regs;
        Addi t,'17;
        Blt 1,(t);

        Move Yp,Y;      # Output is to first sample of output array;
        Move Xl,X;      # Pointer to beginning of input signal;

        Move He,Nwing;  # Max offset + 1 in filter table;
        Add  He,Imp;

        Move IF,Interp; # Nonzero when interpolating filter table;

Lwing:  Setzm v;        # Clear multiply-add accumulator;
        Move Xp,Time;   # Get current time corresponding to "now" in X;
        Caml Xp,EndTime;# Check if all Done;
          Jrst DONE;
        LSH Xp,-Np;     # Pointer to current input sample;
        Add Xp,Xl;
        Movem Xp,Xps;   # Save for for right-wing of filter (left wing first);

        Move Ph,Time;   # Absolute time from Xl;
        Andi Ph,Pmask;  # Position between input samples;
        Setom Inc;      # X increment is backwards;
        Pushj P,Filter; # Perform left wing inner product;

Rwing:  Move Xp,Xps;    # Get back to "now" input sample;
        Addi Xp,1;      # Bump forward 1;
        Setcm Ph,Ph;    # This is offset of corr. coeff. in right wing;
        Andi Ph,Pmask;  # Mask off leading 1's;
        Movei Inc,1;    # Now going forward through X;
        Pushj P,Filter; # Perform right wing inner product;

        Imul v,LpScl;   # Normalize for unity filter gain;
        ASH v,-NLpScl;
        Trne v,2^(Nhg-1);       # Need to round?;
          Addi v,2^(Nhg-1);     # Yup;
        ASH v,-Nhg;     # Remove guard bits;
        Movem v,(Yp);   # Deposit output;
        Addi Yp,1;      # NO CHECK ON OUTPUT ARRAY BOUNDS;
        Move t,Dtb;     # Time increment;
        Addm t,Time;    # Move to next sample;
        Jrst Lwing;     # Do Next sample;

Filter: Move Ho,Ph;     # Np bits specifying time since last X sample;
        Imul Ho,Dhb;    # Get (interpolated) offset into filter table;
        LSH Ho,-Np;     # Offset in (interpolated) table;
Floop:  Move Hp,Ho;     # Place offset into Hp;
        LSH Hp,-Na;     # Shift off interpolation bits to get integer part;
        Add Hp,Imp;     # Add in base address of table;
        Caml Hp,He;     # If we have overrun the table;
          Popj P,;      #   return happily;
        Move t,@Hp;     # Get IR sample;
        Jumpe IF,Nofi;  # If interpolating filter IR table;
          Move Hdp,Ho;  #   Compute integer part of IR address as before;
          LSH Hdp,-Na;  #   But this time;
          Add Hdp,ImpD; #   Access the difference table H[n+1]-H[n];
          Move 0,@Hdp;  #   (For linear interpolation);
          Move a,Ho;    #   Get interpolation bits;
          Andi a,Amask; #     which are the lower Na bits;
          Imul 0,a;     #   Multiply by interpolation factor;
          ASH 0,-Na;    #   a is logically between 0 and 1;
          Add t,0;      #   t is now interpolated filter coefficient;
NoFi:   Imul t,@Xp;     # Multiply coeff by the input sample;
        ASH t,-Nhxn;    # Leave some guard bits but come back some;
        Add v,t;        # Filter output;
        Add Ho,Dhb;     # IR step;
        Add Xp,Inc;     # Input signal step. NO CHECK MADE FOR ARRAY BOUNDS;
        Jrst Floop;

DONE:   Sub Yp,Y;
        Movem Yp,1;     # Return number of output samples;
        Move t,Regs;    # Restore AC's 2 and up;
        Addi t,2;
        Hrlz 0,t;
        Addi 0,2;
        Blt 0,'17;
END "srcf";

  END "SrcUD";
# Set up lowpass filter.

  If NewFilter is FALSE, we attempt to read the interpolation lowpass
  filter from a standard disk file. Otherwise we design the lowpass
  filter (a Kaiser-windowed sinc function) here.
;
  IF NOT NewFilter THEN
  BEGIN "Try to find the right disk file"
    INTEGER Fchan,Brk;
    BOOLEAN Eof,FAIL;
    STRING Fname;
    Fname _ "F"&CVS(Nmult)&"T"&CVMS(Nhc)&".SRC[1,JOS]"; # Construct filename;
    # Original default filename is  "F13T9.SRC[1,JOS]";
    OUTSTR(Tab&"Reading file DSK:"&Fname&Crlf);
    OPEN(Fchan_GETCHAN,"DSK",'17,0,0,200,Brk,Eof);
    LOOKUP(Fchan,Fname,FAIL);
    IF FAIL THEN BEGIN PRINT("File not found",CrLf); NewFilter _ TRUE; END ELSE
    BEGIN "ReadFilterFile"
    # 
        Filter file format:  The first word of the file is (scaleFactor,Length),
        then there are 127 zeros (to finish a record), then there are "Length"
        16-bit impulse response values in the right wing of the impulse response.
        (Length=Npc*(Nmult+1)/2+1 where originally Npc=2^9, and Nmult=13.) 
        At the next record boundary (one record = 128 words, one word = 36 bits 
        [haha]), there is a similar table of impulse-response successive 
        differences: Diff[i]=Imp[i+1]-Imp[i].
    ;

      INTEGER Wd;
      Wd _ WORDIN(Fchan);       # First word of file is [ScaleFactor,,Length];
      LpScl _ Wd LSH -18;       # (Integer) Scale factor has to be positive;
      Nwing _ Wd LAND '777777;  # Mask off table length;
      ARRYIN(Fchan,Imp[0],Nwing); # Array of 16-bit filter coefficients;
      ARRYIN(Fchan,ImpD[0],Nwing);# Array of 16-bit filter coeff. differences;
#     Account for increased filter gain when using Factors less than 1;
      IF Factor<1 THEN LpScl _ LpScl*Factor + 0.5;
    END "ReadFilterFile";
    RELEASE(Fchan);
  END "Try to find the right disk file";

  IF NewFilter THEN
  BEGIN "Make lowpass filter"
    REAL DCgain,Scl,Maxh;
    INTEGER i,Dh;
    PRINT(" Setting up lowpass filter . . . ");
    # FirLpf is in JAMLIB. It designs a KaiserWindow*Sinc lowpass filter;
    FirLpf(ImpR,Nwing,.5*Froll,Npc,Beta);
#
        Compute the DC gain of the lowpass filter, and its maximum
        coefficient magnitude. Scale the coefficients so that the 
        maximum coefficient just fits in Nh-bit fixed-point, and compute
        LpScl as the NLpScl-bit (signed) scale factor which when multiplied 
        by the output of the lowpass filter gives unity gain. LpScl is 
        stored for the case Factor GEQ 1.
;
    DCgain_0;
    Dh _ Npc;  # Filter sampling period for factors greater than or equal to 1;
    FOR i_Dh STEP Dh UNTIL Nwing-1 DO DCgain_DCgain+ImpR[i];
    DCgain_2*DCgain+ImpR[0]; # DC gain of real coefficients;
    FOR i_0 STEP 1 UNTIL Nwing-1 DO Maxh _ Maxh MAX ABS(ImpR[i]); # ImpR[0] typ.;
    Scl _ (2^(Nh-1)-1)/Maxh; # Map largest coefficient to 16-bit maximum;
    LpScl _ ABS(2^(NLpScl+Nh)/(DcGain*Scl));
    IF LpScl>(2^18-1) THEN USERERR(0,1,
        " Oops! Filter scale factor does not fit in halfword."&
        " Reduce NlpScl or try another filter");
#
   Scale filter coefficients for Nh bits and convert to integer
;
    IF ImpR[0]<0 THEN Scl_-Scl; # Need positive first value for LpScl storage;
    FOR i_0 STEP 1 UNTIL Nwing-1 DO ImpR[i] _ ImpR[i]*Scl;
    FOR i_0 Thru Nwing-1 DO Imp[i] _ ImpR[i] + 0.5; # Round it;
#   ImpD makes linear interpolation of the filter coefficients faster;
    FOR i_0 Thru Nwing-2 DO ImpD[i] _ ImpR[i+1] - ImpR[i] + 0.5; 
    ImpD[Nwing-1] _ Imp[Nwing-1]; # Last coeff. not interpolated;
    PRINT("Done.",CrLf);

    BEGIN "write"
      INTEGER PROCEDURE CALLI(INTEGER Type;INTEGER AC1(0));
      START!CODE
        MOVE 1,AC1;
        CALLI 1, @Type;
      END;
      STRING PROCEDURE PPN; RETURN(CVXSTR(CALLI('400071,0)));

      IF EQU(PPN,"  1JOS") THEN
      BEGIN "Write lowpass filter"
        INTEGER Fchan,Brk;
        BOOLEAN Eof,FAIL;
        STRING Fname;
        Fname _ "F"&CVS(Nmult)&"T"&CVMS(Nhc)&".SRC[1,JOS]";
        OUTSTR(Tab&"Writing file DSK:"&Fname&Crlf);
        OPEN(Fchan_GETCHAN,"DSK",'17,0,0,200,Brk,Eof);
        ENTER(Fchan,Fname,FAIL);
        IF FAIL THEN PRINT("Can't enter file",CrLf) ELSE
        BEGIN
          INTEGER Wd;
#         Imp[0] _ (Imp[0] LAND '777777) LOR (LpScl LSH 18);
          Wd _ (LpScl LSH 18) + Nwing;
          WORDOUT(Fchan,Wd);
          ARRYOUT(Fchan,Imp[0],Nwing);
#         Imp[0] _ (Imp[0] LAND '777777);
          ARRYOUT(Fchan,ImpD[0],Nwing);
        END;
        RELEASE(Fchan);
      END "Write lowpass filter";
    END "write";
    CkFIR(Imp,Nwing,Factor*Npc MIN Npc,InterpFilt);
  END "Make lowpass filter"
  ELSE
  IF TRACE THEN CkFIR(Imp,Nwing,Factor*Npc MIN Npc,InterpFilt);
  IF TRACE THEN PRINT(" LpScl = ",LpScl,CrLf);

# Inner Loop;

  Xoff _ ((Nmult+1)/2)*(1/Factor MAX 1); # Reach of lowpass filter wing;
  Xoff _ Xoff + 10;  # Give it some creeping room;
  Nin _ Nx + 2*Xoff; # Number of points to read each buffer;
  SETFORMAT(0,2);    # For buffer begin-time print-out;
  IF InpF(#chans)=1 THEN
  BEGIN "mono"
    INTEGER Yp,Xp,Time,Ncreep;
    Saf INTEGER ARRAY X[0:Nin-1],Y[0:Ny-1];
    Saf REAL ARRAY Xr[0:Nin-1],Yr[0:Ny-1];
    Yp _ 0;             # Current sample and length of output file;
    Xp _ 0;             # Current "now"-sample pointer for input;
    Time _ Xoff LSH Np; # Current-time pointer for converter;
    
    RtScl _ .001;       # Convert milliseconds to seconds;
    PRINT("At time (Itime, Xtime, Otime): ");
    DO
    BEGIN "RI"
      INTEGER RunTime,Nout,Novfl;
      PRINT(CVF(Xp/InpF(Clock))," ");
      IF Xp-Xoff+Nin>Tlen THEN 
      BEGIN 
        Nx _ Nx - (Xp-Xoff+Nin-Tlen);
        Nin _ Nx + 2*Xoff;
      END;
      RunTime _ CALL(0,"RUNTIM");
      IF FastInput THEN I16B1C(InpF(Channel),Xp-Xoff,Nin,X) ELSE
      BEGIN
        Sandi(InpF(Channel),Xp-Xoff,Nin,Xr,Tlen,TRUE,InpF(Pack));
        Novfl _ RtoI16(Xr,X,Nin);
        IF Novfl THEN PRINT(" Overflowed 16 bits on input ",Novfl," times.",CrLf);
      END;
      PRINT("(",CVF((RunTime _ CALL(0,"RUNTIM") - RunTime)*RtScl),",");
      IF Factor GEQ 1 THEN
        Nout _ SrcUp(X,Y,Factor,Time,Nx,Nwing,LpScl,Imp,ImpD,InterpFilt) ELSE
        Nout _ SrcUD(X,Y,Factor,Time,Nx,Nwing,LpScl,Imp,ImpD,InterpFilt);
      PRINT(CVF((RunTime _ CALL(0,"RUNTIM") - RunTime)*RtScl),",");

      IF Trace THEN
      BEGIN
        PRINT(CrLf," Time='",CVOS(Time)," = ",Time LSH -Np," LSH Np");
        PRINT(" Nout =",Nout);
      END;
      Time _ Time - (Nx LSH Np); # Move converter Nx samples back in time;
      Xp _ Xp + Nx; # Advance by number of samples processed;
      Ncreep _ (Time LSH -Np) - Xoff;
      IF Trace THEN PRINT(" Crept ",Ncreep,CrLf);
      IF Ncreep NEQ 0 THEN 
      BEGIN
        Time _ Time - (Ncreep LSH Np);  # Remove time accumulation;
        Xp _ Xp + Ncreep;               # and add it to read pointer;
      END;
      IF Nout>Ny THEN USERERR(0,1,"Output array overflow");
      RunTime _ CALL(0,"RUNTIM");
      IF FastOutput THEN W16B1C(OutF(Channel),Nout,Y) ELSE
      BEGIN
        I16toR(Y,Yr,Nout);
        IF OutF(Pack)=4 THEN 
        BEGIN
          INTEGER i;
          FOR i_0 Thru Nout-1 DO IF ABS(Yr[i])>1 THEN 
          BEGIN 
            Novfl_Novfl+1;
            Yr[i]_Yr[i]/ABS(Yr[i]);
          END;
          IF Novfl>0 THEN 
            PRINT(" Output Overflows 16 bits. Clipped ",Novfl," times.",CrLf);
        END;
        Sando(OutF(Channel),Yp,Nout,Yr,OH,Yp,TRUE,OutF(Pack));
      END;
      PRINT(CVF((CALL(0,"RUNTIM") - RunTime)*RtScl),") ");
    END "RI"
    UNTIL Xp GEQ Tlen-Xoff;
    IF NOT FastOutput THEN Sando(OutF(Channel),0,0,Yr,OH,Yp,TRUE,OutF(Pack));
  END "mono"
  ELSE
  BEGIN "oops"
    PRINT(Crlf&"Sorry, can only do 1-channel files.");
    CALL(0,"EXIT");
  END "oops";
END "ALAR";
# update header;

  s_"From "&Idev&":"&Ifile&Crlf&
    "Comment = "&InpF(Text)&Crlf&
    "Sampling rate conversion by factor of "&CVF(Factor)&Crlf;

  Whead (Head, OutF(Clock), OutF(Pack), OutF(#chans), OutF(#ticks), Maxamp,s);
  USETO (OutF(Channel), 1);
  ARRYOUT (OutF(Channel), Head [1], 128);
  RELEASE(OutF(Channel));
END "SRC".