; variable to save score to
variable *granulate-score* 
         
;; FILE-GRAIN creates a grain sampled from a file
;; Parameters:
;;   FILENAME - the file to granulate
;;   OFFSET - the time in seconds where the grain will start (default is 0)
;;   SPEED - samples are stretched by 1/SPEED, causing pitch shift
;;      (default is 1 - no stretch), interpolation is linear, which is not
;;      the highest quality, but granular synthesis tends to be noisy anyway
;; Implicit parameters:
;;   the nominal duration (stretch) controls the grain duration
;;
;; raised-cosine is an envelope used to fade the grain in and out
;;
define function file-grain(filename: "", offset: 0, speed: 1.0)
  begin
    with grain = s-read(filename, time-offset: offset, 
                        dur: get-duration(1) * speed)
    return (sound(grain) ~~ (1.0 / speed)) * raised-cosine()
  end

;; make-granulate-score makes a score of grains
;; Grains are selected by scanning through the file from
;; fileoffset to fileend, but the file location is perturbed
;; by a random number in the range from 0 to randomness, and 
;; grains are randomly omitted depending density.
;;
;; Parameters:
;;   FILEOFFSET - where to start reading from the file (default 0s)
;;   RANDOMNESS - the nominal file position for the grain (based on
;;     FILEOFFSET, IOI and STRETCH) is perturbed by a random offset
;;     from 0 to RANDOMNESS (default 1s), controlling smearing effect
;;   FILEEND - where to stop reading from the file (default 10s).
;;     This is the *nominal* end point, but due to RANDOMNESS,
;;     grains may be read starting as much as FILEEND + RANDOMNESS.
;;     Grains that extend beyond the actual end of the file will
;;     not be added to the score. The maximum extent that could be
;;     computed is FILEEND + RANDOMNESS + GRAIN-DUR. If FILEEND is
;;     NIL, it will be replaced with the actual duration of the file.
;;   GRAIN-DUR - the duration of each grain (default 50ms)
;;   IOI - spacing of overlapping output grains
;;   STRETCH - stretch factor, resulting score duration is
;;     approximately (FILEEND - FILEOFFSET) * STRETCH
;;   DENSITY - fraction of grains that are inserted into score,
;;     0 means none, 1 means all, default is 1
;;   SPEED - Playback speed of the grains
;;   
;; The output grains are uniformly spaced with an inter-onset
;; interval of ioi. The file is scanned by adding ioi / stretch
;; to the fileoffset for each grain, so if stretch > 1, the 
;; file is scanned slowly and the output sounds stretched. If
;; stretch < 1, the output will sound compressed in time.
;;
define function make-granulate-score(filename: "",
                                     fileoffset: 0, 
                                     randomness: 1, 
                                     fileend: 10,
                                     grain-dur: 0.05,
                                     ioi: 0.025,
                                     stretch: 1.0,
                                     density: 1.0,
                                     speed: 1)
  begin
    with score, score-time = 0, ignore = s-read(filename),
         file-dur = snd-read-dur(*rslt*)
    if ! fileend then set fileend = file-dur
    set ignore = nil ; no sense keeping the file open
    loop
      while fileoffset < fileend
      if rrandom() <= density then
        begin with offset = fileoffset + rrandom() * randomness
          ;; avoid reading anything beyond file-dur
            if offset + grain-dur <= file-dur then
              set score @= list(score-time, grain-dur, 
                              list(quote(file-grain),
                                   :filename, filename,
                                   :offset, offset,
                                   :speed, speed))                  
        end
      set score-time += ioi,
          fileoffset += ioi / stretch
      finally 
        begin
          return reverse(score)
        end
    end
  end 

;; raised-cosine or "Hanning" window. The cosine is generated by LFO,
;; so it is at the control sample rate (default = 2205 Hz). Since LFO is
;; normally a sine, we set the initial  to 270 to make it a negative
;; cosine. The duration obeys the current stretch factor.
function raised-cosine()
  return 0.5 * (1 + lfo(1.0 / get-duration(1), 1, *sine-table*, 270))

;; This function generates the sound object for putting a pulsing amplitude
;; filter over the audio.
define function gen-pulsing(filename)
    return timed-seq(make-granulate-score(filename: filename,
                                          fileoffset: 0, 
                                          randomness: 0, 
                                          fileend: nil,
                                          grain-dur: 0.28571428571,
                                          ioi: 0.28571428571,
                                          stretch: 1,
                                          density: 1,
                                          speed: 1))

;; This function generates the sound object for garbling the audio with
;; sparse stutters
define function gen-garble(filename)
    return timed-seq(make-granulate-score(filename: filename,
                                          fileoffset: 0, 
                                          randomness: 0, 
                                          fileend: nil,
                                          grain-dur: 0.03571428571,
                                          ioi: 0.03571428571,
                                          stretch: 1,
                                          density: 0.7,
                                          speed: 2))

;; This function generates the sound object for randomized
;; long-duration grain sampling. (Grain Dur is one full beat)
define function gen-glitch(filename)
    return timed-seq(make-granulate-score(filename: filename,
                                          fileoffset: 0, 
                                          randomness: 4, 
                                          fileend: nil,
                                          grain-dur: 1.14285714286,
                                          ioi: 0.57142857142,
                                          stretch: 1,
                                          density: 1,
                                          speed: 1))

; AMPLITUDE PULSES
; play gen-pulsing("./composition_input/else.wav")
; play gen-pulsing("./composition_input/drums.wav")


; GARBLING
; play gen-garble("./composition_input/else.wav")

; LONG GLITCH STUTTER
; play gen-glitch("./composition_input/else.wav")