;; getosc.sal - examples to receive and use OSC data
;;
;; Roger B. Dannenberg
;; July 2022

load "sliders.lsp"  ;; get some slider functions
;; *** NOTE: sliders.lsp also turns off Autonorm and sets audio latency 
;; *** to 20ms. Both of these will vastly reduce delay between changing
;; *** a slider and hearing the result.

;; IMPORTANT!!!  YOU MUST ENABLE OSC IN NYQUIST
exec osc-enable(t)

;; XLISP has no sleep! It's wasteful, but the only way to sleep is to 
;; busy wait. Another option would be to play a sound of duration s seconds,
;; but that requires a fairly slow open and close audio device step, so 
;; we'll just do a simple busy-wait:
function sleep(s)
  begin with endtime = get-real-time() + s
    loop while get-real-time() < endtime end
  end

;; ===================================================================
;; OSC Example 1 - print values of slider 1
;;     Run this function with "exec osc-ex1()", then in a shell window
;; run "python3 nyquistosc.py"; you should see that slider 1 gets set 
;; to 10 random values, with a new value every 1 second.
;;
function osc-ex1()
  begin
    loop ;; print every 2/3 seconds so we're sure to catch changes that
         ;; will occur every 1 second. 2/3 * 30 iterations = 20 s runtime
      repeat 30
      exec sleep(2.0 / 3.0)
      print "Slider 1 value is ", get-slider-value(1)
    end
  end

;; ===================================================================
;; OSC Example 2 - play a sound that is filtered by OSC values
;;    Play this function with "play osc-ex2()", then in a shell window
;; run "python3 nyquiustosc.py"; you should hear the sound change as
;; new random slider 1 values arrive.
;;
;; The filter frequency is swept over 5 octaves from C4 to C9 as 
;; slider 1 varies from 0 to 1. Note the use of step-to-hz so that
;; the frequency mapping is exponential. (Linear mapping could be
;; used, but most cutoff frequencies would be very high and would
;; not be obvious.
;;
;; Notice that lpslider(1) has a nominal duration of only 1, but it
;; is stretched like everything else by 30 using "~ 30".
;;
function osc-ex2()
  begin
    return (env(0.1, 0.3, 1, 1, 0.8, 0.7) * 
            lp(buzz(30, c4, lfo(6)),
               step-to-hz(c4 + 60 * lpslider(1)))) ~ 30
  end


;; ===================================================================
;; OSC Example 3 - play a new note and a new pitch whenever slider 1
;; changes value.
;;    Play this function with "play osc-ex3()", then in a shell window
;; run "python3 nyquistosc.py"; you should hear 10 notes. This function
;; will stop responding to OSC after 30 seconds.
;;    Note that with multiple sliders, you could use one slider to 
;; trigger notes or other events and pass parameters with other sliders.
;;    There is no automatic normalization (it is turned off by 
;; the load "sliders.lsp" command), so we scale pluck by 0.5 to prevent
;; clipping.
function osc-ex3()
  begin
    return trigger(detect-change(1, 30), 
                   0.5 * pluck(pitch-from-slider(1)))
  end

;; detect-change(n, dur) - convert a slider signal into a sound that
;; can serve as a trigger, i.e., we want a positive value >0 at each
;; change point. The result has the specified duration, so to "listen"
;; to changes for 30 s, pass 30 for dur.
;;    To detect change, use slope() to get the derivative which is zero 
;; except where there is a change. Then take the absolute value to get
;; positive values for trigger.
;;
function detect-change(n, dur)  ;; n = slider number, dur = duration
  begin
    return s-abs(slope(slider(n, dur)))
  end

;; pitch-from-slider(n) - convert slider value [0 to 1] into a step value
;;     Arbitrarily return a 2 octave range from c4 to c6:
function pitch-from-slider(n)
  return round(c4 + get-slider-value(n) * 24)

play osc-ex3()
