Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Changing arguments interactively #28

Open
jagrg opened this issue Feb 10, 2022 · 12 comments
Open

Changing arguments interactively #28

jagrg opened this issue Feb 10, 2022 · 12 comments

Comments

@jagrg
Copy link
Contributor

jagrg commented Feb 10, 2022

Hi, is it possible to change the steps in the pbjorklund function interactively? For example, using this script, if I change 7 to some other integer while the pattern is playing and eval the expression, the pattern doesn't change. How do I do this?

(pb :pattern
  :instrument :block
  :embed (pbjorklund 7 16 :dur 4))
                     ^

Also, I was reading your tutorial and found a function that changes the pitch by moving the mouse/trackpad up and down, similar to a knob. Is it possible to do the same, but changing the steps instead in the pbjorklund function?

(proxy :foo (sin-osc.ar 440 0 0.2))
(proxy :foo (saw.ar (mouse-y.kr 20 10000 :exponential) 0.2))
@defaultxr
Copy link
Owner

defaultxr commented Feb 10, 2022

Hi!

For example, using this script, if I change 7 to some other integer while the pattern is playing and eval the expression, the pattern doesn't change. How do I do this?

All patterns will repeat infinitely by default, and any changes made to a pattern definition (i.e. pdef or pb in this case) will only apply once the pattern ends and loops.

In the case of pbjorklund, the :dur argument is effectively just a duration multiplier and doesn't actually set a "total duration" before the pattern ends. Since the pattern is repeating forever, it never ends and never has the chance to restart and take the new parameters into effect. There are at least two things you can do to change that in this case:

  • Add a :repeats 1 argument to pbjorklund, so it only plays once before ending. Then any changes you make will take effect when next loop of pbjorklund starts.
  • Add a :end-quant 4 argument to the pb, which will tell the pb that it can end (and thus restart the loop) at any beat that is a multiple of 4.

Thinking about it now, having :dur be only a duration multiplier is probably not the most intuitive behavior. I should probably change the name of that argument to :dur-mul or similar so it's more clear, and then maybe make a new :dur argument that acts as if setting :dur-mul to the specified value and :repeats to 1 simultaneously, so :dur sets the pattern's total duration just like it does with pfindur and the like.

Also, I was reading your tutorial and found a function that changes the pitch by moving the mouse/trackpad up and down, similar to a knob. Is it possible to do the same, but changing the steps instead in the pbjorklund function?

Unfortunately, it's not super convenient to do that--at least, not yet.

Mainly, mouse-y and mouse-x are SuperCollider UGens that run on the SuperCollider server, so the only way you can use them in regular Lisp functions would be to set up some kind of synth that is playing on the server, and regularly polling those UGens to send their values back to the Lisp side. This would have to be done with the send-reply UGen and the cl-collider:add-reply-responder function, like so:

(defparameter *mouse-x-value* 0)

(cl-collider:add-reply-responder
 "/mouse-x"
 (lambda (node trig &rest value)
   (declare (ignorable node trig))
   (setf *mouse-x-value* (car value))))

(proxy :mouse-x-value-poller
       (let ((mouse-x (mouse-x.kr 0 1 :linear 0)))
         (send-reply.kr (impulse.kr 10) "/mouse-x" (list mouse-x))))

The proxy will start a synth on the server that polls mouse-x.kr 10 times per second (thanks to the (impulse.kr 10)) and sends its value back to the Lisp side. The reply-responder receives the value and sets *mouse-x-value* to its value.

The proxy and reply-responder can both be changed while they're running, and the old versions will be replaced with the new versions, as long as you keep their names the same, similar to how pdef and pb work.

Normally at this point I would say "now you can simply use something like (pf (round (* 16 *mouse-x-value*))) for pbjorklund's pulses parameter and you're good to go", but actually, it looks like pbjorklund wasn't written to accept patterns for most of its parameters, pulses and steps included. I've fixed that just now, so you can either copy and eval the new definition of that method to get the changes, or do a git pull and restart your Lisp and recompile/reload cl-patterns. Or just wait until the next Quicklisp update if you're very patient O:)

I should also note that even with this change, pbjorklund will only take into account changes at the start of each "loop". So if pulses is changed in the middle of the pattern, the change won't be heard until the stop-quant is reached, or the end of the specified number of repeats in pbjorklund. It would be a little more complex to implement it so that pbjorklund changes immediately even in the middle of the pattern, so I can't guarantee when that will be ready. But I do intend to look into that sort of thing more in the future, for all patterns in the library, not just that one (it will of course be customizable when implemented, rather than forcing all patterns to always behave that way).

Here's the full version of the code for you:

(defparameter *mouse-x-value* 0)

(cl-collider:add-reply-responder
 "/mouse-x"
 (lambda (node trig &rest value)
   (declare (ignorable node trig))
   (setf *mouse-x-value* (car value))))

(proxy :mouse-x-value-poller
       (let ((mouse-x (mouse-x.kr 0 1 :linear 0)))
         (send-reply.kr (impulse.kr 10) "/mouse-x" (list mouse-x))))

(pb :pattern
  :instrument :block
  :embed (pbjorklund (pf (let ((v (round (* 16 *mouse-x-value*))))
                           (format t "~&~s~%" v)
                           v))
                     16
                     :dur 4
                     :repeats 1))

(play :pattern)

In any case, let me know if any of this doesn't work for you, or is unclear. In the meantime I'll leave this issue open so I remember to look at making pbjorklund's :dur parameter more intuitive, and also possibly implementing some kind of pattern or function to get the mouse positioning directly from the Lisp side.

@jagrg
Copy link
Contributor Author

jagrg commented Feb 11, 2022 via email

@jagrg
Copy link
Contributor Author

jagrg commented Feb 11, 2022 via email

@defaultxr
Copy link
Owner

If you're just using the code snippet I posted, the 0 is probably from the format expression, which is printing the value of *mouse-x-value* every time the pattern restarts. If it's not changing even when you move your mouse, it sounds like either the proxy or the reply-responder is not working properly. Try changing the reply responder to this to see if it's actually getting any values from the server:

(cl-collider:add-reply-responder
 "/mouse-x"
 (lambda (node trig &rest value)
   (declare (ignorable node trig))
   (format t "~&Got value ~s from the server.~%" (car value))
   (setf *mouse-x-value* (car value))))

If you run that and you don't see any messages like Got value X from the server then it means either the proxy isn't running, the server itself isn't running, or maybe there is a typo in your code somewhere.

As for not hearing any sound, I'm guessing you restarted your Lisp, and maybe you forgot to start the SuperCollider server again (or connect it via JACK) after doing that. Are you sure you ran these?

(setf *s* (make-external-server "localhost" :port 4444))
(server-boot *s*)
(jack-connect)

If so, it could also be that you didn't re-send the defsynth for your block synthdef. I think by default, cl-collider doesn't store synthdefs permanently, so you have to re-evaluate them when you reboot the server and/or your Lisp.

Let me know if you're still having any issues after trying those suggestions.

@jagrg
Copy link
Contributor Author

jagrg commented Feb 11, 2022

I see the "Got value 0.11145834 from the server" messages and the number changes as I move the mouse. Now with the original function, when I evaluate the pb function I see:

; in: PB :PATTERN
;     (* 16 CL-PATTERNS::*MOUSE-X-VALUE*)
; 
; caught WARNING:
;   undefined variable: CL-PATTERNS::*MOUSE-X-VALUE*
; 
; compilation unit finished
;   Undefined variable:
;     *MOUSE-X-VALUE*
;   caught 1 WARNING condition

@defaultxr
Copy link
Owner

Looks like you're evaluating the pb in the CL-PATTERNS package, but evaluated the defparameter in another package. If you put some in the REPL, they will be evaluated in the package shown by the REPL prompt. So maybe you evaluated them in the CL-USER or SC-USER package but then evaluated the pb in CL-PATTERNS.

@defaultxr
Copy link
Owner

Probably the best solution would be to define your own package, and make it :use cl-collider and cl-patterns, so you have the symbols from both available in it. Then do (in-package #:your-package-name) to ensure any code evaluated after that is within the defined package. For example:

(defpackage #:my-cool-package
  (:use #:cl
        #:cl-collider
        #:cl-patterns)
  (:shadowing-import-from #:cl-patterns
                          #:play
                          #:stop
                          #:quant))

(in-package #:my-cool-package)

;; your code goes here

The :shadowing-import-from is needed because cl-patterns exports some symbols that have the same name as symbols that cl-collider exports. That clause tells defpackage that we want the ones from cl-patterns.

Alternatively, this is shorter and works just as well, as long as you have uiop (which you should if you have a recent-enough version of asdf):

(uiop:define-package #:my-cooler-package
  (:mix #:cl
        #:cl-patterns
        #:cl-collider))

(in-package #:my-cooler-package)

;; your code goes here

@jagrg
Copy link
Contributor Author

jagrg commented Feb 11, 2022

Let's take a step back. What's wrong with the script I'm using, and how do I fix it?

(ql:quickload :cl-collider)
(in-package :sc-user)
(setf *s* (make-external-server "localhost" :port 4444))
(server-boot *s*)
(jack-connect)

(in-package :cl-collider)
			 
(defsynth block ((gain 1))
  (let* ((env (line.kr 4 0 .03 :act :free))
         (sig (sin-osc.ar 500 0 env)))
    (out.ar 0 (pan2.ar sig 0 gain))))

(defparameter *mouse-x-value* 0)

(cl-collider:add-reply-responder
 "/mouse-x"
 (lambda (node trig &rest value)
   (declare (ignorable node trig))
   (setf *mouse-x-value* (car value))))

(proxy :mouse-x-value-poller
       (let ((mouse-x (mouse-x.kr 0 1 :linear 0)))
         (send-reply.kr (impulse.kr 10) "/mouse-x" (list mouse-x))))

(ql:quickload :cl-patterns/supercollider)
(cl-patterns:start-backend :supercollider)
(in-package #:cl-patterns)

(start-clock-loop :tempo 130/60)

;; This one plays
;; (pb :pattern
;;   :instrument :block
;;   :embed (pbjorklund 7 16 :dur 4 :repeats 1))

;; This one fails
(pb :pattern
  :instrument :block
  :embed (pbjorklund (pf (let ((v (round (* 16 *mouse-x-value*))))
                           (format t "~&~s~%" v)
                           v))
                     16
                     :dur 4
                     :repeats 1))

(play :pattern)
;; (stop t)

@defaultxr
Copy link
Owner

defaultxr commented Feb 11, 2022

Because you have (pb :pattern ...) under (in-package #:cl-patterns), it will look for all symbols (including the symbol *mouse-x-value*) in the cl-patterns package. The reason you get that warning is because the symbol *mouse-x-value* doesn't exist in cl-patterns; it exists in cl-collider, because you did (defparameter *mouse-x-value* 0) under (in-package #:cl-collider).

If you do the following instead, it should work (I just tested it from a fresh restart of SBCL):

(ql:quickload '(:cl-collider :cl-patterns/supercollider))

(in-package :sc-user)
(setf *s* (make-external-server "localhost" :port 4444))
(server-boot *s*)
(jack-connect)

(uiop:define-package #:cl-patterns-scratch
  (:mix #:cl
        #:cl-patterns
        #:cl-collider))

(in-package #:cl-patterns-scratch)

(cl-patterns:enable-backend :supercollider)

(start-clock-loop :tempo 130/60)

(defsynth block ((gain 1))
  (let* ((env (line.kr 4 0 .03 :act :free))
         (sig (sin-osc.ar 500 0 env)))
    (out.ar 0 (pan2.ar sig 0 gain))))

;; this symbol's full name is cl-patterns-scratch::*mouse-x-value* here:
(defparameter *mouse-x-value* 0)
;; ...whereas in your previous reply, its full name was cl-collider::*mouse-x-value*

(cl-collider:add-reply-responder
 "/mouse-x"
 (lambda (node trig &rest value)
   (declare (ignorable node trig))
   (setf *mouse-x-value* (car value))))

(proxy :mouse-x-value-poller
       (let ((mouse-x (mouse-x.kr 0 1 :linear 0)))
         (send-reply.kr (impulse.kr 10) "/mouse-x" (list mouse-x))))

(pb :pattern
  :instrument :block
  :embed (pbjorklund (pf (let ((v (round (* 16 *mouse-x-value*))))
                           (format t "~&~s~%" v)
                           v))
                     16
                     :dur 4
                     :repeats 1))

(play :pattern)

The reason this works is because we make a new package, #:cl-patterns-scratch, which includes all the symbols from #:cl, #:cl-patterns, and #:cl-collider together. Then when we define *mouse-x-value* it's in the #:cl-patterns-scratch package, same as the rest of the code, so Lisp knows exactly which symbol you're trying to reference.

So basically, the problem you're facing is due to the way symbols work in Common Lisp; each symbol belongs to a package, and when you're in one package, you won't be able to access the symbols of another package unless you either prefix the symbol name with the name of the package (i.e. package-name::symbol-name), switch to the correct package with in-package, or use-package the package containing the desired symbol from the package you want to use it from.

If you're unfamiliar with how symbols and packages interact in Common Lisp, I would recommend reading a guide like this one or similar to familiarize yourself with them, since you basically can't use CL without using symbols (and thus packages). Otherwise you're likely to run into similar problems in the future.

Hope that clarifies things a bit, let me know if not.

@jagrg
Copy link
Contributor Author

jagrg commented Feb 11, 2022 via email

@defaultxr
Copy link
Owner

No problem, happy to be of help :)

defaultxr added a commit that referenced this issue Oct 7, 2023
see `pmouse`, `pmousex`, `pmousey`

also adds `skip-unless` macro to support tests for these patterns. if
there is no supported display server, we skip testing them.

related to the request in #28
@defaultxr
Copy link
Owner

Hi. Regarding your request for using the mouse position in patterns, I have added an initial implementation of this in 9ff3f25.

It shells out to the xdotool command, so you'll need to have that installed (and be running an X server). However, it seems to be very slow to get mouse position this way (as I suspected it might be), so it's possible this won't be performant enough to use in real time.

A better implementation would probably use FFI to bypass shell command invocation entirely, and I may do that eventually. But maybe the current version would still be usable for you until then. If not, the best option would probably be to continue using the alternative I provided previously.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants