apply-at, random real generation, and bugging questions

123 views
Skip to first unread message

saltyk...@gmail.com

unread,
May 1, 2013, 12:30:01 AM5/1/13
to over...@googlegroups.com
So I wanted to define a function which randomly generates wave-like sounds. I have a number of questions...

1. I have read the apply-at odoc, but am not completely sure about it interprets its arguments. In a simple kick and hat loop example I realized as long as argument 1 is smaller than arg 3 and arg 3 is set to the correct time between cycles everything will work fine. What happens when arg 1 is greater than arg 3 is less clear. Can anyone explain?
2. Is there a function I can use to generate reals that if plotted would form a logarithmic, exponential, normal, etc. distribution?
3. Why am I getting the error below?


(defn ocean [time]
        (at (+ 0 time) (* (env-gen (lin-env (+ .02 (rand 3)) (+ 0.01 (rand 1)) (+ 1 (rand 6))) 1 1 0 1 FREE)
                                                          (pink-noise)
                                                          (+ 0.3 (rand 0.7))))
        (apply-at (+ (rand 2) time) ocean (+ (rand 2) time) []))

#'user/ocean

(ocean (now))

ClassCastException overtone.sc.machinery.ugen.sc_ugen.SCUGen cannot be cast to java.lang.Number  clojure.lang.Numbers.multiply (Numbers.java:146)

Thanks everyone!

Rob Elsner

unread,
May 1, 2013, 12:52:01 AM5/1/13
to over...@googlegroups.com
apply-at takes a timestamp, and this timestamp needs to be absolute
relative to 1970ish.

For some badly written Clojure see my project:

https://github.com/thatsnotright/chaotic-theremin/blob/master/src/chaotictheremin/core.clj#L63

where I take the start time since program start, and incrementally add
a value (from a CSV file) to it to schedule changes via apply-at.
> --
> You received this message because you are subscribed to the Google Groups
> "Overtone" group.
> To unsubscribe from this group and stop receiving emails from it, send an
> email to overtone+u...@googlegroups.com.
> For more options, visit https://groups.google.com/groups/opt_out.
>
>

Sam Aaron

unread,
May 1, 2013, 4:02:50 AM5/1/13
to over...@googlegroups.com
Hi there,

I've updated the apply-at docstring. Does the following make more sense?

"Scheduled function appliction. Works identically to apply, except
that it takes an additional initial argument: ms-time. If ms-time is
in the future, function application is delayed until that time, if
ms-time is in the past function application is immediate.

Can be used to immplement the 'temporal recursion' pattern. This is
where a function has a call to apply-at at its tail:

(defn foo
[t val]
(println val)
(let [next-t (+ t 200)]
(apply-at next-t #'foo [next-t (inc val)])))

(foo (now) 0) ;=> 0, 1, 2, 3...

The fn foo is written in a recursive style, yet the recursion is
scheduled for application 200ms in the future. By passing a function
using #'foo syntax instead of the symbole foo, when later called by
the scheduler it will lookup based on the symbol rather than using
the instance of the function defined earlier. This allows us to
redefine foo whilst the temporal recursion is continuing to execute."

Sam

---
http://sam.aaron.name

saltyk...@gmail.com

unread,
May 1, 2013, 7:20:02 PM5/1/13
to over...@googlegroups.com
Yes, but I already understood what when the time-ms argument and the new argument to foo are the same. I also understand what happens when time-ms is less than the new foo argument. What I'm having difficulty understanding is what happens when time-ms is greater than the new foo argument. Take the following example:

(defn loop-beats [time]
  (at (+ 0 time) (kick) )
  (at (+ 400 time) (hat) )
  (at (+ 800 time) (kick) )
  (at (+ 1200 time) (hat) )
  (apply-at (+ x time) #'loop-beats [(+ y time)]))
(loop-beats (now))

Here is the beat pattern, after the initial loop, for various values x and y. Beats with commas signify eighth notes, underscores quarter note rests, and forward slashes eighth note rests. The values in parenthesis represent the the initial delay before the loop starts. Here quarter notes are 400ms.

1600-2000 1600
k h k h

2200 1600
(/) k,h k h _

2400 1600
(_) kh k h _

2600 1600
(_ /) kh,k h _ _

2800 1600
(_ _ _) kkh h _ _

3000 1600
(_ _ _ /) khk,h _ _ _

3200+ 1600
(_ _ _ _ ) khkh _ _ _

3200 3200
(_ _ _ _) k h k h _ _ _ _

1600 2400
(_ _) k h k h _ _

What is seemingly illogical about this is that it seems that like these should be the results for x-400. I.e. if our first recursion starts at our initial time (IT) + 2000, but is using time IT +1600 for our new value. At IT+1600 there should be an initial quarter note delay. At IT +2000 we should start our recursive loop. Then the first kick is is schedule for 400ms ago and the first hat is scheduled for the current time (IT+2000), so they should be played together. At IT+2400 there should be a single kick and at IT+2800 there should be a single hat. The first loop through apply-at changed our time argument to IT+1600 and since our time-ms argument adds 2000 to this we should begin our loop at IT+3600. Therefore at IT+3200 there should be a quarter note rest. Now our argument for time is IT+3200 so at IT+3600 we should hear the kick scheduled 400ms ago along with the hat scheduled for the current time IT+1600+1600+400. Therefore our beat pattern should be (_) kh k h _, however this is the beat pattern for x=2400 and not x=2000. What gives? How am I conceptualizing this wrong?

saltyk...@gmail.com

unread,
May 1, 2013, 7:27:15 PM5/1/13
to over...@googlegroups.com
On another note, why am I getting this "ClassCastException" error? I tried to simplify the apply-at part, but maybe that's not what is wrong.

(defn ocean [time]
        (at (+ 0 time) (* (env-gen (lin-env (+ 0.02 (rand 3)) (+ 0.01 (rand 1)) (+ 0.1 (rand 6))) 1 1 0 1 FREE)

                                                          (pink-noise)
                                                          (+ 0.3 (rand 0.7))))
        (apply-at (+ 10 time) #'ocean [(+ 10 time)]))
#'user/ocean
user> (ocean (now))

ClassCastException overtone.sc.machinery.ugen.sc_ugen.SCUGen cannot be cast to java.lang.Number  clojure.lang.Numbers.multiply (Numbers.java:146)

As you can test for yourself the following does work:


(definst fupa [] (* (env-gen (lin-env (+ 0.02 (rand 3)) (+ 0.01 (rand 1)) (+ 0.1 (rand 6))) 1 1 0 1 FREE)
                                                          (pink-noise)
                                                          (+ 0.3 (rand 0.7)))))

Sam Aaron

unread,
May 2, 2013, 4:42:06 AM5/2/13
to over...@googlegroups.com
Hey Noah,

when using the 'recursion through time' pattern, it doesn't really make sense to use different values for the apply-at time-ms argument and a time argument to the recursive function.

What are you trying to achieve? What are your x and y vals?

The main aim of temporal recursion is to create a function which calls itself in the future. Passing a time argument to the function allows it to perform actions based off that time (which is more accurate and convenient than asking for the current time with (now)). Using that time to generate a new time for the at macro is exactly the kind of thing this is good for (which is what you're doing).

One note though - you should probably be using apply-by rather than apply-at for this kind of activity. apply-at applies the function exactly at the specified time, apply-by applies it slightly before - allowing you to schedule things ahead of time:

* If you're using the fn to schedule other things w.r.t. a time, consider apply-by
* If you're using the fn to perform actions at a specific time, consider apply-at

Sam

---
http://sam.aaron.name

Sam Aaron

unread,
May 2, 2013, 6:18:47 AM5/2/13
to over...@googlegroups.com
Hi there Noah,

On 2 May 2013, at 00:27, saltyk...@gmail.com wrote:

> On another note, why am I getting this "ClassCastException" error? I tried to simplify the apply-at part, but maybe that's not what is wrong.
>
> (defn ocean [time]
> (at (+ 0 time) (* (env-gen (lin-env (+ 0.02 (rand 3)) (+ 0.01 (rand 1)) (+ 0.1 (rand 6))) 1 1 0 1 FREE)
> (pink-noise)
> (+ 0.3 (rand 0.7))))
> (apply-at (+ 10 time) #'ocean [(+ 10 time)]))
> #'user/ocean
> user> (ocean (now))
> ClassCastException overtone.sc.machinery.ugen.sc_ugen.SCUGen cannot be cast to java.lang.Number clojure.lang.Numbers.multiply (Numbers.java:146)
>


This is because you're mixing Overtone's synth language with pure Clojure. fns such as (pink-noise) and (lin-env) only make sense in the context of a synth design. In pure Clojure land, they simply return data structures describing ugens. Only when you place them within a synth design, and ship that synth design to the SuperCollider server, are sounds able to be made (which is precisely what the defsynth and definst macros do).

The reason why you're getting the exception is that you're calling forms such as this:

(* (sin-osc) 2)

This makes no sense in pure Clojure land as * is only implemented to work with numbers. Here, we're trying to multiply the output of a sine oscillator by 2. To make Clojure happy we need to wrap this in the following macro:

(with-overloaded-ugens
(* (sin-osc) 2))

this will now work and the correct ugen tree datastructure is generated. The the with-overloaded-ugens macro overloads standard Clojure fns such as * to be ugen-aware, and do the right thing when working with ugens as args.

There's typically no need to explicitly do this as definst and defsynth implicitly wrap your synth design in a with-overloaded-ugens form for you. You were seeing this error as you were trying to evaluate ugens outside of a synth design context.

> As you can test for yourself the following does work:
>
>
> (definst fupa [] (* (env-gen (lin-env (+ 0.02 (rand 3)) (+ 0.01 (rand 1)) (+ 0.1 (rand 6))) 1 1 0 1 FREE)
> (pink-noise)
> (+ 0.3 (rand 0.7)))))

Yep! This is because you're now in the happy context of a synth design.

Sam

---
http://sam.aaron.name

saltyk...@gmail.com

unread,
May 17, 2013, 12:24:57 PM5/17/13
to over...@googlegroups.com
Thanks Sam. I have tried to rewrite this a number of ways including the following, but keep running into errors. Although I would appreciate help with making this a functional function, I know my issues stem from a misunderstanding of how ugens work with Clojure. Can anyone recommend some documentation and help me sort this one out?

(defn ocean [time]
  (let [attack (+ 0.02 (rand 4))
        sustain (+ 0.02 (rand 2))
        release (+ 0.02 (rand 6))
        vol (+ 0.3 (rand (0.7)))]
(at (+ 0 time)  (with-overloaded-ugens (* (env-gen (lin-env attack sustain release) 1 1 0 1 FREE)
                                          (pink-noise)
                                          vol))))
    (let [next-t (+ 400 (rand 8000) time)]
    (apply-at next-t #'ocean [next-t])))

On Wednesday, May 1, 2013 12:30:01 AM UTC-4, saltyk...@gmail.com wrote:

Sam Aaron

unread,
May 17, 2013, 1:46:10 PM5/17/13
to over...@googlegroups.com
Hi Noah,

On 17 May 2013, at 17:24, saltyk...@gmail.com wrote:

> Thanks Sam. I have tried to rewrite this a number of ways including the following, but keep running into errors. Although I would appreciate help with making this a functional function,

What are you trying to do?

Sam

---
http://sam.aaron.name


Tim Ramsey

unread,
Sep 28, 2013, 9:06:41 AM9/28/13
to over...@googlegroups.com
(apologies for necroing this thread, but it seemed appropriate to the topic)

I have tried replacing my apply-at with apply-by but I am getting an unresolved symbol error.  Any hints as to what I have mis-configured?  My project.clj looks like:

(defproject synth "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "Eclipse Public License"
            :url "http://www.eclipse.org/legal/epl-v10.html"}
  :dependencies [[org.clojure/clojure "1.5.1"]
                 [org.clojure/math.numeric-tower "0.0.1"]
                 [overtone "0.8.1"]
                 ])


This is what I get in my nrepl session:

user> (use 'overtone.live)
--> Loading Overtone...
--> Booting internal SuperCollider server...

    _____                 __
   / __  /_  _____  _____/ /_____  ____  ___
  / / / / | / / _ \/ ___/ __/ __ \/ __ \/ _ \
 / /_/ /| |/ /  __/ /  / /_/ /_/ / / / /  __/
 \____/ |___/\___/_/   \__/\____/_/ /_/\___/

   Collaborative Programmable Music. v0.8


Hey Tramsey, I feel something magical is only just beyond the horizon...

nil
user> overtone.music.time/apply-at
#<time$apply_at overtone.music.time$apply_at@1a8af95>
user> overtone.music.time/apply-by
CompilerException java.lang.RuntimeException: No such var: overtone.music.time/apply-by, compiling:(NO_SOURCE_PATH:1)
user>


Thanks,
-Tim Ramsey

Sam Aaron

unread,
Sep 28, 2013, 9:52:52 AM9/28/13
to over...@googlegroups.com
Hi there,

apply-by is in the 0.9 development version. You can obtain a recent snapshot by specifying overtone "0.9.0-SNAPSHOT" in your project dependencies.

Sam

---
http://sam.aaron.name

Tim Ramsey

unread,
Sep 28, 2013, 10:01:24 AM9/28/13
to over...@googlegroups.com
Thanks, Sam.  I had actually had tried 0.9.0 thinking it was probably in a later version.  The 0.9.0-SNAPSHOT works great.

-Tim
Reply all
Reply to author
Forward
0 new messages