* Fourier Series Function for SPICE
* This script is offered here for learning purposes, even if it is outdated
* and superseeded by the spec function and especially by the much faster fft function.
* You may use this script in conjunction with e.g. a ringoscillator output (see
* the ngspice manual, chapter 17).

.control
  begin

* Variable argc delivers the number of command line parameters given by the user 
* after the 'spectrum' command  
   if ($argc lt 4)
 	 echo   Error: Too few arguments.
	 echo   '      'Spectrum produces a plot containing a fourier series transformation of 
	 echo   '      'the specified vectors
	 echo   usage: spectrum startfreq stop step vec [[vec] ...]
	 goto bottom
   end

* Check if vectors 'time' and any input vector(s) are available
* argv[n] delivers the command line entries after the 'spectrum' command,
* starting with argv[1]. $argv[4-len] delivers the value of all tokens, 
* starting with postion 4 till the end of the command line    
   if ( time eq time ) 
      foreach vec $argv[4-len]
         if ( $vec eq $vec )
	     else
            goto bottom
	     end
      end
   else
      echo '       'Spectrum can not work without a time vector from a transient analysis.
      goto bottom
   end

   * generate a new plot entitled 'scratch', which will hold intermediate
   * results and will be discarded after their evaluation.   
   set dt=$curplot
   set title=$curplottitle
   set curplot=new
   set scratch=$curplot

   * A vector 'span' is created in the 'scratch' plot to hold the time difference
   * of the transient simulation. {$dt}.time allows to access the 'time' vector
   * from the dt plot (which is normally named 'tranx' with x a consecutoive
   * integer number, depending on the amount of transient simulations already run
   * in the present job.   
   let span={$dt}.time[length({$dt}.time)-1]-{$dt}.time[0]

* Calculate the number of steps in all of the spectra to be evaluated below
   if ($argv[3] gt 0.999/span)
       let fpoints= ( $argv[2] - $argv[1] ) / $argv[3] +1
	   if (fpoints < 2)
	       echo frequency start stop or step not correctly specified
		   goto reset
	   end
   else
       echo   Error: time span is not long enough for a step frequency of $argv[3] Hz
	   goto reset
   end
   let lent = length({$dt}.time)
   set lent = "$&lent"
   let nyquist = {$lent}/2/span
   if ($argv[2] gt nyquist)
       echo   Error: The nyquist limit is exceeded, try a frequency less than "$&nyquist" Hz
	   goto reset
   end
   set fpoints="$&fpoints"

   * generate a new plot to hold the spectra   
   set curplot=new
   set spec=$curplot
   set curplottitle=$title
   set curplotname='Spectrum Analysis'
   
* argv[3] is the third agrgument from the input line 
* spectrum 1 1000MEG 10MEG v(out25) 
* that is the delta frequency
* The fcn vector(n) creates a vector of length n, its elements have
* the values 0, 1, 2, 3, ..., n-2, n-1. Each element then is multiplied 
* with the frequency step value. 
   let frequency=vector( $fpoints )*$argv[3]

* Add an frequency offset to each element of vector 'frequency'
* to suppress the (typically) large dc component.
   dowhile frequency[1] < ( $argv[1] + 1e-9 )
       let frequency = frequency + $argv[3]
   end

* For each input vector given on the command line,
* create a new vector for complex numbers   
   foreach vec $argv[4-len]
       let $vec = vector( $fpoints ) + j(vector( $fpoints ))
	   reshape $vec [{$fpoints}]
   end

* $scratch is a plot for intermediate results, will be destroyed during cleanup
* $dt is the plot with the original data
* $spec is a plot for storing the spectrum   
   set curplot=$scratch

   * some test   
   let npers=1
   let test = span-2/$argv[3] + 1e-9
   while test > 0
       let npers = npers + 1
       let test = test-1/$argv[3]
   end

   * Do the spectrum calculations  
   let ircle = 2*pi*max(-1,({$dt}.time-{$dt}.time[{$lent}-1])*{$argv[3]}/npers)
   let win = 1 - cos(ircle)
   let ircle = npers*ircle
   let circle = ircle * ({$spec}.frequency[0]/$argv[3] - 1)
   let k=vector( $fpoints ) 
   foreach k $&k
      let circle = circle + ircle 
      foreach vec $argv[4-len]
	      let tmp = win*{$dt}.{$vec}
          let {$spec}.{$vec}[{$k}] = 2*(mean(cos(circle)*tmp),mean(sin(circle)*tmp))
      end
   end

* plot (and write) the generated spectrum   
   set curplot = $spec
   settype frequency frequency
   foreach vec $argv[4-len]
      let spectrum = mag({$vec})
      plot spectrum
      write specout.out spectrum
   end
   
* If you have an oscillator, fimd its frequency 
* as maximum of vector spectrum or goto end (uncomment next line)
*  goto cleanup
   set curplot=$scratch  
   let counter = 0
   let contents = 0
   let freqmax = 0
   let spectrum = {$spec}.spectrum
 
   foreach spectrum $&spectrum
       if counter > 4
         if ( contents < $spectrum )
            let contents = $spectrum
			set count = "$&counter"
			let freqmax = {$spec}.frequency[{$count}]
         end
      end
	  let counter = counter + 1
   end
   
   echo   
   echo Osc. frequency at "$&freqmax" Hz   
   echo
   goto cleanup
   
   label reset
   set curplot=$dt   
   label cleanup
   destroy $scratch
   unset fpoints dt scratch spec vec k title lent
   label bottom

 end
