# Filtering the spectral magnitude as defined by:

#@inproceedings{Boeck2012Onset,
#   author = {Sebastian B\"{o}ck and Florian Krebs and Markus Schedl},
#   title = {Evaluating the online capabilities of onset detection methods},
#   booktitle = {Proceedings of the 13th International Society for Music Information Retrieval Conference (ISMIR'12)},
#   year = {2012},
#   pages = {49--54}
#} 


# INPUT: 	fa: output of fftArray() function
#			ctrl: control object

# OUTPUT: 	fa like in input but with modificated spectrum

specFilter = function(fa, ctrl)
{
  if (ctrl$spec.filter == "yes"){
   S=fa$spec
   FB = filter.boeck(ctrl$window.size/2, 44100, bands=12)
   R = t(FB)%*%S
   fa$spec=R
   return(fa)
  } 
  else{
   # no filtering
   return(fa)
  }
 }


# filtering functions of Böck et al. (2012)

filter.boeck = function(ffts, fs, bands=12, fmin=27.5, fmax=16000, equal=FALSE){

  # Creates a new Filter object instance.
  # :param ffts: number of FFT coefficients
  # :param fs: sample rate of the audio file
  # :param bands: number of filter bands [default=12]
  # :param fmin: the minimum frequency [in Hz, default=27.5]
  # :param fmax: the maximum frequency [in Hz, default=16000]
  # :param equal: normalize each band to equal energy [default=False]
  
  # samplerate
  self.fs = fs
  # reduce fmax if necessary
  if (fmax > fs / 2)
  {fmax = fs / 2}
  # get a list of frequencies
  frequencies = self.frequencies(bands, fmin, fmax)
  # conversion factor for mapping of frequencies to spectrogram bins
  factor = (fs / 2.0) / ffts
  # map the frequencies to the spectrogram bins
  frequencies = round(frequencies / factor)
  # only keep unique bins
  frequencies = unique(frequencies)
  # filter out all frequencies outside the valid range
  w= which(frequencies < ffts)
  frequencies = frequencies[w]
  
  # number of bands
  bands = length(frequencies) - 2
  if (bands < 3) {print("cannot create filterbank with less than 3 frequencies")}
  # init the filter matrix with size: ffts x filter bands
  self.filterbank = matrix(rep(0, ffts* bands), ncol=bands)
  
  # process all bands
  for (band in 1:bands) {
    # edge & center frequencies
    start = frequencies[band]+1   # +1 wegen python indizierung ab 0!
    mid = frequencies[band+1]+1
    end = frequencies[band+2]+1
    # create a triangular filter

    self.filterbank[start:(end-1), band] = self.triang(1, mid-start+1, end-start+1)
    #browser()
  }
  return(self.filterbank)
}



self.frequencies = function(bands, fmin, fmax, a=440) {
  # Returns a list of frequencies aligned on a logarithmic scale.
  # :param bands: number of filter bands per octave
  # :param fmin: the minimum frequency [in Hz]
  # :param fmax: the maximum frequency [in Hz]
  # :param a: frequency of A0 [in Hz, default=440]
  # :return a list of frequencies
  # Using 12 bands per octave and a=440 corresponding to the MIDI notes.
  
  # factor 2 frequencies are apart
  factor = 2.0 ^ (1.0 / bands)
  # start with A0
  freq = a
  frequencies = freq
  # go upwards till fmax
  while (freq <= fmax) {
    # multiply once more, since the included frequency is a frequency
    # which is only used as the right corner of a (triangular) filter
    freq = freq * factor
    frequencies = c(frequencies, freq)
  }
    
  # restart with a and go downwards till fmin
  freq = a
  while (freq >= fmin) {
    # divide once more, since the included frequency is a frequency
    # which is only used as the left corner of a (triangular) filter
    freq = freq/factor
    frequencies = c(frequencies, freq)
  }
  # sort frequencies
  frequencies = sort(frequencies)
  # return the list
  return (frequencies)
}

self.triang = function(start, mid, end, equal=FALSE){
  #Calculates a triangular window of the given size.
  #param start: starting bin (with value 0, included in the returned filter)
  #param mid: center bin (of height 1, unless norm is True)
  #param stop: end bin (with value 0, not included in the returned filter)
  #param equal: normalize the area of the filter to 1 [default=False]
  #return a triangular shaped filter
  
  # height of the filter
  height = 1
  # normalize the height
  if (equal)
  {height = 2 / (end - start)}
  # init the filter
  triang_filter = rep(0, end - start+1)
  # rising edge
  triang_filter[start:mid] = seq(0,1,length.out=mid-start+1)
  # falling edge
  triang_filter[mid:end] = seq(1,0,length.out=end-mid+1)
  # return
  return (triang_filter[1:end-1])
}

