Search code examples
clojureovertone

How does midicps work in overtone?


I'm trying to add in some microtonal functionality to overtone, but I'm having issues when it comes to dealing with the function midicps, which seems to be a requirement for dealing with midi input.

This is what I want:

(definst instrument [note 64 amp 0.5 gate 1]
  (* amp (lpf (lf-tri (foo note) ) 1100)
  (env-gen (adsr 0.1 0.2 2 2 0.3) gate :action FREE)))

foo is a function that takes a midi value and maps it to a value in hz (sort of like midicps), however, the above code doesn't run correctly.

For the sake of testing, I've defined foo as:

(defn foo [x] (if (= 64 x) 880 440))

Thus, (instrument 64) should play a note an octave higher than any other foo, but it doesn't.

I believe what is happening is that when I run (instrument x), it passes something else (a midi event?) to foo instead of the integer itself.

Any time I try running midi->hz over midicps, I get the error:

CompilerException java.lang.ClassCastException: overtone.sc.machinery.ugen.sc_ugen.SCUGen 
cannot be cast to java.lang.Number, compiling:(form-init7628662755818333410.clj:1:1)

Calling midi->hz on note without midicps yields the same error.

How do I convert this ugen (or midi event) to an integer representing the midi note?

Note: I'm using midi-poly-player to add the midi handler for my instrument. I can get my pitch mapping function to work if I just make a regular note-on event handler, but I would much rather use midi-poly-player so I don't have to re-implement all of it's functionality.


Solution

  • You're mixing scsynth ugens and Clojure code when you call the foo function like this. Inside the definst, it is a macro taking that Clojure code and converting it to supercollider synthesizer code. I don't actually understand why the code behaves like it does, but I think I can show you how to make it do what you want. Just keep the code within the definst macro.

    Here is the midi->hz function in Clojure. midicps does this as a ugen in supercollider for you:

    (defn midi->hz
      "Convert a midi note number to a frequency in hz."
      [note]
      (* 440.0 (java.lang.Math/pow 2.0 (/ (- note 69.0) 12.0))))
    

    You can just inline that note->frequency conversion in your definst:

    (definst instrument2 [note 64 amp 0.5 gate 1]
      (let [freq (* 440.0 (pow 2.0 (/ (- note 69.0) 12.0)))]
        (* amp (lpf (lf-tri freq) 1100)
           (env-gen (adsr 0.1 0.2 2 2 0.3) gate :action FREE))))
    

    and listen to the standard 12 notes per scale output like:

    (def x (instrument2 69))
    (ctl x :gate 0)
    (def x (instrument2 70))
    (ctl x :gate 0)
    (def x (instrument2 71))
    (ctl x :gate 0)
    

    to adjust for more tones-per-octave, for example, this gives 24...

    (definst instrument3 [note 64 amp 0.5 gate 1]
      (let [freq (* 440.0 (pow 2.0 (/ (- note 69.0) 24.0)))]
        (* amp (lpf (lf-tri freq) 1100)
           (env-gen (adsr 0.1 0.2 2 2 0.3) gate :action FREE))))
    
    (def x (instrument3 69))
    (ctl x :gate 0)
    (def x (instrument3 70))
    (ctl x :gate 0)
    (def x (instrument3 71))
    (ctl x :gate 0)
    

    I hear a series of smaller steps. I hope you do, too.

    Cheers,

    Roger