program spectrum

      program spectrum

      implicit integer(a-z)
      parameter (maxlen=512)
      integer over

      integer*2 status

      byte ss(maxlen)
      character*(maxlen) cdata

      logical average

      external get_wave

      equivalence (ss,cdata)
c
           
      over = 10
      average = .true.
    1 continue
      ldata = get_wave(cdata,samplesPsec)
c      ldata = get_sine(ss,maxlen,samplesPsec)
c      ldata = get_square(ss,maxlen,samplesPsec)
c      ldata = get_rndm(ss,maxlen,samplesPsec)
      if(ldata.le.0) goto 99
      call do_fft(ss,ldata,samplesPsec,average,over)
      goto 1
    2 write(6,*) ' Finished'
      stop 0
   99 write(6,*) ' Error ',ldata,' reading data'
      end


*=*=*=*= get_rndm.html =*=*=*=*

integer function get_rndm

      integer function get_rndm(ss,maxlen,samplesPsec)
      integer samplesPsec

      byte ss(maxlen)
      real val

      get_rndm = maxlen

      samplesPsec = maxlen

      do i=1,maxlen

         val = 127.*random(i)
         ss(i) = val

      end do
      end   

*=*=*=*= get_sine.html =*=*=*=*

integer function get_sine

      integer function get_sine(ss,maxlen,samplesPsec)
      integer samplesPsec

      byte ss(maxlen)
      real val,v1,v2,pi2

      save icall,pi2

      data icall /0/
c
      if(icall.eq.0) then
         icall = 1
         write(6,*) 'Enter frequency 1 in Hertz'
         read(5,*) hertz1

         write(6,*) 'Enter frequency 2 in Hertz'
         read(5,*) hertz2

         pi2 = 6.0*asin(sqrt(3.)*0.5)
      endif
      get_sine = maxlen

      samplesPsec = 7.1*max(hertz1,hertz2)
      v1 = pi2*real(Hertz1)/real(samplesPsec)
      v2 = pi2*real(hertz2)/real(samplesPsec)
      do i=1,maxlen

         val = 64.*(2.+ cos(v1*real(i-1)) +
     &                   cos(v2*real(i-1)))
         ss(i) = val

      end do
      end   
      

*=*=*=*= get_square.html =*=*=*=*

integer function get_square

      integer function get_square(ss,maxlen,samplesPsec)
      integer samplesPsec

      byte ss(maxlen)
      real val,v1,v2,pi2

      save icall,pi2

      data icall /0/
c
      if(icall.eq.0) then
         icall = 1
         write(6,*) 'Enter frequency 1 in Hertz'
         read(5,*) hertz1

         write(6,*) 'Enter frequency 2 in Hertz'
         read(5,*) hertz2

         pi2 = 6.0*asin(sqrt(3.)*0.5)
      endif
      get_square = maxlen

      samplesPsec = 7.1*max(hertz1,hertz2)
      v1 = pi2*real(Hertz1)/real(samplesPsec)
      v2 = pi2*real(hertz2)/real(samplesPsec)
      do i=1,maxlen

         a = -1.
         if(cos(v1*real(i-1)).gt.0.) a = 1.
         b = -1.
         if(cos(v2*real(i-1)).gt.0.) b = 1.
         if(hertz2.eq.0) b = 0.
         val = 64.*(2.+a+b)
         ss(i) = val

      end do
      end   
      
      
      

*=*=*=*= get_wave.html =*=*=*=*

integer function get_wave

      integer function get_wave(ss,samplesPsec)
      implicit integer(a-z)
      parameter (recl=512)
      integer*2 status

      character*127 cin

      character*(*) ss

      logical info

c
      structure /format_chunk/
       union
        map
         character*4 fmt

         integer*4   Length

         integer*2   FormatTag

         integer*2   Channels

         integer*4   SamplesSec

         integer*4   BytesSec

         integer*2   BlockSize

         integer*2   BitsSample

        end map
        map
         character*24 read

        end map
       end union
      end structure
      record /format_chunk/ fmt
c
      structure /header_chunk/
       union
        map
         character*4 riff

         integer*2   data1

         integer*2   data2

         character*4 filetype

        end map
        map
         character*12 read

        end map
       end union
      end structure
      record /header_chunk/ header
c
      structure /data_chunk/
       union
        map
         character*4  data

         integer*2    data1

         integer*2    data2

        end map
        map
         character*8  read

        end map
       end union
      end structure
      record /data_chunk/ datac
c
      save icall,info

c
      data icall /0/
c
      if(icall.eq.0) then
         icall = 1
         info = .false.
         call getarg(2,cin,status)
         if(status.gt.0.and.cin(:1).eq.'I') info = .true.
         call getarg(1,cin,status)
         if(status.le.0) 
     &     stop 'Usage : SPECTRUM <.wav file name> '
         if(cin(1:1).eq.'?') 
     &     stop 'Usage : SPECTRUM <.wav file name> '
c
         open(1,file=cin(:status),err=99,status='old',
     &        iostat=iostat,form='binary',recl=recl)
c
         lread = len(header.read)+len(fmt.read)+len(datac.read)
         len_data = recl-lread

         read(1) header.read,fmt.read,datac.read,ss(1:len_data)
         if(info) then
            write(6,*) 'RIFF      ',header.riff
            write(6,*) 'data1     ',header.data1
            write(6,*) 'data2     ',header.data2
            write(6,*) 'filetype  ',header.filetype
            write(6,*) 'fmt       ',fmt.fmt
            write(6,*) 'fmt length',fmt.length
            write(6,*) 'fmt tag   ',fmt.formattag
            write(6,*) 'Channels  ',fmt.channels
            write(6,*) 'Samples   ',fmt.samplessec
            write(6,*) 'Bytes     ',fmt.bytessec
            write(6,*) 'Blocksize ',fmt.blocksize
            write(6,*) 'Bits/smple',fmt.bitssample
            write(6,*) 'Data      ',datac.data
            write(6,*) 'Data1     ',datac.data1
         endif
         if(fmt.formattag.ne.1.or.
     &      header.filetype.ne.'WAVE') then
            stop 'Not a .wav file'
         endif
         get_wave = len_data

         samplesPsec = fmt.samplessec
         return
      endif
      iostat = 5000
      read(1,end=99,err=99) ss

      get_wave = recl

      return
   99 get_wave = -iostat

      end 



c
*=*=*=*= do_fft.html =*=*=*=*

subroutine do_fft

      subroutine do_fft(data,n,rate,average,count_over)
      implicit integer(a-z)
      parameter (segment=256,length=segment/2)
      complex in(segment)
      real d,sum(length),out(length),rdata(segment)
      byte data(n)
      integer rate,count,count_over

      logical average

      save count,sum

      data count /0/
   
      if(count.eq.0) then
         do i=1,length

            sum(i) = 0.0
         end do
      endif
c
      ipos = 0
    1 if(ipos+segment.gt.n) goto 2
      do i=1,segment

         d = real(iand(data(ipos+i),#FF))
         rdata(i) = d

         in(i) = cmplx( d , 0.0 )
      end do
      call fft(in,segment,-1)
c
c average
c
      count = count+1
      if(average) then
         do i=1,length

            sum(i) = sum(i)+abs(in(i))
            out(i) = sum(i)/real(count)
         end do
      else
         do i=1,length

            out(i) = abs(in(i))
         end do
         count_over = 0
      endif
c
      call display(rdata,out,segment,rate,count_over,count)
      ipos = ipos + segment

      if(average.and.count.ge.count_over) count = 0
      goto 1
    2 continue
c
      end

*=*=*=*= DISPLAY.html =*=*=*=*

SUBROUTINE DISPLAY

      SUBROUTINE DISPLAY(in,out,segment,rate,count_over,inum)
      include 'fgraph.fd'

c
      common /plot/ hz_low,hz_high,db_low,db_high,vlow,vhigh

      real hz_low,hz_high,db_low,db_high,vlow,vhigh

      integer rate,segment,length,count_over,inum

      integer*2 halfy,fullx,fully,xborder,yborder

      integer*2 status,handle1

      real in(segment),out(segment)
      character*5 ctext

      byte diagmask(8)
c
      record /videoconfig/ myscreen
      record /wxycoord/     xy
c
      save icall,halfy,fullx,fully,xborder,yborder

      data icall /0/
      data diagmask /8*#FF/
c
      length = segment/2
      if(icall.eq.0) then
         icall = 1
         hz_low = 0.0
         hz_high = real(rate)*0.5
         db_low = 0.0
         db_high = 10.0
         vlow = 0.
         vhigh = 255.
         status = setvideomode($MAXRESMODE)
         if(status.eq.0) stop 'Unable to start Graphics'
         handle1 = wggetactiveqq()
         status = wgcloseqq(handle1)
         handle1 = wgopenqq('Frequency Response'//char(0))
         status = wgsetactiveqq(handle1)
         status = setvideomode($VRES16COLOR)
c         status = clickqq(QWIN$TILE)
         call getvideoconfig(myscreen) 
         halfy = myscreen.numypixels/2
         fullx = myscreen.numxpixels-1
         fully = myscreen.numypixels-1
         xborder = fullx/5
         yborder = fully/8
         status = registerfonts("dummy")
         status = setfont('h8w8b')
         if(status.lt.0) then
c            write(6,*) ' Setting substitute font'
            status = setfont('f')
         endif
c
c Set axes and tick marks
c
         call setviewport(xborder,fully-yborder,fullx,
     &                    fully)
         status = setwindow(.true.,0.,0.,real(segment),1.)
         status = setcolor(6)
         call moveto_w(0.,1.,xy)
         status = lineto_w(real(segment),1.)
         ic = 0 
         do i=1,segment,10
            ic = ic+1
            call moveto_w(real(i),1.,xy)
            status = lineto_w(real(i),0.95)
            if(mod(ic,2).eq.1) then
               status=lineto_w(real(i),0.9) 
               write(ctext,'(f5.3)') real(i)/real(rate)
               call verttext(ctext)
            endif
         end do
         status = setcolor(8)
         call moveto_w(0.,0.10,xy)
         call outgtext(' Elapsed seconds from sample start')

         call setviewport(xborder,halfy-yborder+1,fullx,halfy)
         status = setwindow(.true.,hz_low,0.,hz_high,1.)
         status = setcolor(6)
         call moveto_w(hz_low,1.,xy)
         status = lineto_w(hz_high,1.)
         mhzh = nint(hz_high/1000.)*1000
         mhzl = ifix(hz_low/100.)*100
         ic = 0
         do i=mhzl,mhzh,100
            ic = ic + 1
            d = real(i)
            call moveto_w(d,1.,xy)
            status = lineto_w(d,0.95)
            if(mod(ic,2).eq.1) then
               status = lineto_w(d,0.9)
               write(ctext,'(i5)') i

               call verttext(ctext)
            end if
         end do
         call moveto_w(hz_low,0.10,xy)
         status = setcolor(8)         
         call outgtext(' Hertz')
         call setfillmask(diagmask)
      endif
c
c called every time for vertical axes and info
c
      call setviewport(0,0,xborder-1,fully)
      status = setwindow(.true.,0.,0.,1.,1.)
      status = setcolor(0)
      status = rectangle_w($GFILLINTERIOR,0.,0.,1.,1.)      
      status = setcolor(10)
      write(ctext,'(i5)') segment

      call moveto_w(0.,0.95,xy)
      call outgtext(ctext//' channels')
      call moveto_w(0.,0.90,xy)
      write(ctext,'(f5.1)') db_high

      call outgtext(' dB max '//ctext)
      call moveto_w(0.,0.85,xy)
      write(ctext,'(i5)') inum

      call outgtext('Sample '//ctext)
      call moveto_w(0.,0.80,xy)
      if(count_over.ne.0) then
         write(ctext,'(i5)') count_over

         call outgtext('out of '//ctext)
      endif



      call setviewport(xborder,halfy+yborder,fullx,
     &                 fully-yborder)
      status = setwindow(.true.,0.,vlow,real(segment),vhigh)
      status = setcolor(0)
      status = rectangle_w($GFILLINTERIOR,0.,vlow,real(segment),vhigh)
      call moveto_w(0.,vlow,xy)
      status = setcolor(3)
      do i=1,segment

         d = real(i)
         status = lineto_w(d,in(i))
      end do

      
      call setviewport(xborder,0,fullx,halfy-yborder)
      status = setwindow(.true.,hz_low,db_low,hz_high,db_high)
      channel_to_hz = real(rate)/real(segment)
      barw2 = channel_to_hz*0.5
      barhm = db_high

      call setfillmask(diagmask)
      amax = -9999.
      offset = 10000.
      if(out(1).gt.0.) offset = 10000./out(1)
      do i=2,length

         dB = out(i)
         if(dB.gt.amax) amax = dB

         d = real(i)*channel_to_hz-barw2*0.5
         status = setcolor(0)
         status = 
     &   rectangle_w($GFILLINTERIOR,d,db_low,d+barw2,db_high)
         status = setcolor(2)
         status = 
     &   rectangle_w($GFILLINTERIOR,d,db_low,d+barw2,dB)
      end do      
c
c Set scale to 1.5 times max amplitude for next sample
c
      db_high = 1.5*amax

      end


c
c  The following FFT code is from:
c
c  Arthur Wouk (wouk@brl-vgr)
c
c  *******************
c  Fast Fourier Transform
c  *******************
c
*=*=*=*= FFT.html =*=*=*=*

SUBROUTINE FFT

        SUBROUTINE FFT (X, N, K)
C       FFT COMPUTES THE (FAST) FOURIER TRANSFORM OF THE VECTOR X
C       (A COMPLEX ARRAY OF DIMENSION N). SOURCE: Ferziger; Numerical
C       methods for engineering applications.
C
C       X = DATA TO BE TRANSFORMED; ON RETURN IT CONTAINS THE TRANSFORM.
C       N = SIZE OF VECTOR. MUST BE A POWER OF 2 (<32769). C K="1" FOR FORWARD TRANSFORM. C K="-1" FOR INVERSE TRANSFORM. C IMPLICIT INTEGER (A-Z) INTEGER SBY2,S

        REAL GAIN, PI2, ANG, RE, IM

        COMPLEX X(N), XTEMP, T, U(16), V, W

        LOGICAL NEW

        DATA    PI2,GAIN,NO,KO /6.283185307, 1., 0, 0/
C
C       TEST FIRST CALL?
C
        NEW = ( NO .NE. N)
        IF ( .NOT. NEW) GO TO 2
C
C       IF FIRST CALL COMPUTE LOG2 (N).
C
        L2N = 0
        NO = 1
    1   L2N = L2N + 1
        NO = NO + NO

        IF (NO .LT. N) GO TO 1
        GAIN = 1./N

        ANG = PI2*GAIN

        RE = COS (ANG)
        IM = SIN (ANG)
C
C       COMPUTE COMPLEX EXPONENTIALS IF NOT FIRST CALL
C
    2   IF (.NOT. NEW .AND. K*KO .GE. 1) GO TO 4
        U(1) = CMPLX (RE, -SIGN(IM, FLOAT(K)))
        DO 3 I = 2,L2N

           U(I) = U(I-1)*U(I-1)
    3   CONTINUE
        K0 = K

C
C       MAIN LOOP
C
    4   SBY2 = N

        DO 7 STAGE = 1,L2N

           V = U(STAGE)
           W = (1., 0.)
           S = SBY2

           SBY2 = S/2
           DO 6 L = 1,SBY2

                DO 5 I = 1,N,S

                   P = I + L- 1
                   Q = P + SBY2

                   T = X(P) + X(Q)
                   X(Q) = ( X(P) - X(Q))*W

                   X(P) =T

    5           CONTINUE
                W = W*V

    6      CONTINUE
    7   CONTINUE
C
C       REORDER THE ELEMENTS BY BIT REVERSAL
C
        DO 9 I = 1,N

           INDEX = I-1
           JNDEX = 0
           DO 8 J = 1,L2N

                JNDEX = JNDEX+JNDEX

                ITEMP = INDEX/2
                IF (ITEMP+ITEMP .NE. INDEX) JNDEX = JNDEX + 1
                INDEX = ITEMP

    8      CONTINUE
           J = JNDEX + 1
           IF (J .LT. I) GO TO 9
           XTEMP = X(J)
           X(J) = X(I)
           X(I) = XTEMP

    9   CONTINUE
C
C       FORWARD TRANSFORM DONE
C
        IF (K .GT. 0) RETURN
C
C       INVERSE TRANSFORM
C
        DO 10 I = 1,N

           X(I) = X(I)*GAIN

   10   CONTINUE
        RETURN
        END
*=*=*=*= verttext.html =*=*=*=*

SUBROUTINE verttext

      SUBROUTINE verttext (text)
      
      INCLUDE 'FGRAPH.FD'


      CHARACTER*(*)  text

      CHARACTER*1    ch

      INTEGER*2      status, loop, ipos, jpos, indent

      RECORD         / xycoord / xy, xyorig
      RECORD         / fontinfo / fi

C     Get the beginning location for output
      CALL getcurrentposition(xyorig)
      ipos = xyorig.xcoord
      jpos = xyorig.ycoord
      
C     The font info is used to center the letters, and determine how
C       far each letter should be below the previous one
      status = getfontinfo(fi)
      
C     Output one char of the string at a time, in a descending column
      do loop = 1, len(text)
        ch = text(loop:loop)
        indent = ipos + (fi.pixwidth-getgtextextent(ch))/2
        CALL moveto(indent, jpos, xy)
        CALL outgtext(ch)
        jpos = jpos + fi.pixheight
      end do
      
C     Set new current position to the end of the vertical text column
      CALL moveto(ipos, jpos, xy)
      END

include 'fgraph.fi' c------------------------------------------------------------------------------- c c SPECTRUM c c Usage: SPECTRUM <.wav file> c c Requires MS Windows 3.1, or Windows NT c c If 'I' is specified as a second parameter then some header info c from the .WAV file is printed. c c You can drag a .WAV file and drop it on SPECTRUM.EXE in the File Manager c as well. c c Program description: c c This program reads as input a .WAV file, decodes it, and passes c the sound samples to an FFT which decomposes them to their c frequency components. For each set of samples, the sample values c are plotted, together with the frequency (spectrum) decomposition. c c The code allows for averaging over n sets of samples (currently set to c 10). The number of samples in a set passed to the FFT must be a power of c 2 (currently set to 256 ['segment' in do_fft]). There are trade-offs with c execution speed and accuracy when this value is changed. c c There are some routines (not called in this version) which generate c sine and square wave samples, and pass them to the FFT. These were put c there for checking purposes. c c The code is Fortran, written for MS Fortran v5.1 . c c In traditional Fortran style, it is sparsely commented! c c This code is Public Domain, but I'd ask you leave my name on it c if you use it for anything else. The actual FFT routine is by c A.Wouk (also Public Domain) whom I thank warmly (wherever he is!). c c J.J.Bunn 1994 c Julian.Bunn@caltech.edu c 100327.317@compuserve.com c c------------------------------------------------------------------------------- *=*=*=*= spectrum .html =*=*=*=*