Skip to content

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

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

Forum Thread #14

Closed
TatriX opened this issue Feb 23, 2021 · 29 comments
Closed

Forum Thread #14

TatriX opened this issue Feb 23, 2021 · 29 comments

Comments

@TatriX
Copy link

TatriX commented Feb 23, 2021

Hello!
I have several questions which aren't big enough for their own issues, so I decided to put them here together.

How to stretch pbjorklund?

Docs says

... so it can be easily multiplied if you want it to be longer or shorter.

I came up with this:

(next-n (pbind :embed (pbjorklund 3 16) :dur (p* (pk :dur) 2)) 16)

Is there a simpler way do that?

How to use pmeta

For example there is a :stretch pmeta key, but I can't wrap my head around it.
Could you please give another example of pmeta usage?


Thank you!

@defaultxr
Copy link
Owner

Hi!

Yes, for pbjorklund, the events it outputs have the sum of their durs normalized to 1, so you can just multiply them by however long you want the pattern to be. There isn't a way of doing that that is built-in to pbjorklund but maybe there should be, perhaps as a &key argument or the like; that'd be much more convenient than having to do the whole :dur (p* (pk :dur) ...) song-and-dance.

pmeta is a bit of a monster so I can't blame you for having trouble with it. And yeah, there isn't much in the way of examples of its use yet and the docstring example is pretty minimal. Another thing I should work on. (Actually tbh pmeta is one of the newer patterns in the library and it might still be a bit buggy, which is why I haven't written a lot on it yet. Basic stuff should work but I can't promise it won't break...)

Here's an example of how to use pmeta and specifically its :stretch key:

(pb :inner
  :instrument :default
  :degree (pseries)
  :dur 1
  :pfindur 4)

(pdef :outer (pmeta :pattern :inner
                    :stretch (pseq (list 2 1 1/2 1/4) 1)))

(play :outer)

In this example, inner is just a basic pattern of four events. outer is the pmeta and if you play it, you'll first hear the inner played twice as slow, then at normal rate, then half as slow, and then 1/4 as slow.

So basically this is similar to doing something like :dur (p* (pk :dur) 2) but each "step" of the pmeta applies to one entire playthrough of the inner pattern. You could also do this for the same effect:

(pb :foo
  :embed (pn (pdef :inner))
  :dur (p* (pk :dur)
           (pr (pseq (list 2 1 1/2 1/4) 1) 4)))

(play :foo)

The only problem with the above is that the 4 in (pr ... 4) would need to always be set as the same length as the inner pattern is, whereas with pmeta it always applies each "step" to a full playthrough of inner regardless of what its length is, or even if its length changes each time it is played.

Hope that makes sense!

Also, another source of confusion I'm seeing in the pmeta docstring is that it's still using the previous design of the pattern where you do something like (pmeta (pbind ...)) rather than just specifying the key/value pairs directly like (pmeta :key value ...)... If you're putting the pbind inside pmeta like that then you may have been stumbling into some problems because of that (sorry about that...)

Anyway just let me know if you have any other questions or anything!

defaultxr added a commit that referenced this issue Feb 26, 2021
note that STEPS is now a required argument, and the OFFSET, DUR,
and REPEATS arguments are keyword arguments
@TatriX
Copy link
Author

TatriX commented Mar 1, 2021

New pbjorklund is lovely, thank you! The only problem with it right now is that it doesn't work very well with :type :midi when :embed:

(next-n (pb :kick :embed (pbjorklund 1 4 :dur 4)) 4)

((EVENT :PDEF :KICK :TYPE :NOTE :DUR 1) (EVENT :PDEF :KICK :TYPE :REST :DUR 1)
 (EVENT :PDEF :KICK :TYPE :REST :DUR 1) (EVENT :PDEF :KICK :TYPE :REST :DUR 1))

Now if I want to send it via MIDI:

(next-n (pb :kick :embed (pbjorklund 1 4 :dur 4) :type :midi) 4)

((EVENT :PDEF :KICK :DUR 1 :TYPE :MIDI) (EVENT :PDEF :KICK :DUR 1 :TYPE :MIDI)
 (EVENT :PDEF :KICK :DUR 1 :TYPE :MIDI) (EVENT :PDEF :KICK :DUR 1 :TYPE :MIDI))

Notes are played every beat, as :type is overwritten.

@defaultxr
Copy link
Owner

Thanks for pointing that out! I've fixed that so it should work as expected now, though you will need to specify the note type before embedding the pbjorklund, like so:

(next-n (pbind :type :midi :embed (pbjorklund 1 4 :dur 4)) 4)

((EVENT :TYPE :MIDI :DUR 1) (EVENT :TYPE :REST :DUR 1)
 (EVENT :TYPE :REST :DUR 1) (EVENT :TYPE :REST :DUR 1)) 

@TatriX
Copy link
Author

TatriX commented Mar 2, 2021

As I'm still learning I often find that I want to stop everything, so I'm using:

(defun stop-all ()
  "Stop all."
  (mapc #'stop (all-pdefs)))

Of course at first I tried to do what was working in cl-collider: called (stop), which gave me an error.
What do you think about defining (stop) as "stop everything"?

@defaultxr
Copy link
Owner

defaultxr commented Mar 2, 2021

I would love to do that, but unfortunately it's not really possible to define a generic function with an optional argument in Common Lisp (at least, not cleanly, as far as I'm aware)...

After defining the following:

(defmethod foo (&optional (bar t))
  (print t)
  (print bar)

(defmethod foo (&optional bar)
  (print 'none))

(defmethod foo (&optional (bar symbol))
  (print 'sym)
  (print bar))

You'll get the following behavior:

> (foo 'foo)
SYM 
FOO 
FOO

> (foo 1)
SYM 
1 
1 (1 bit, #x1, #o1, #b1)

> (foo)
; Debugger entered on #<UNBOUND-VARIABLE SYMBOL {10042EB603}>

The last definition of foo is considered a different function because it doesn't match the signature of the first one.

One workaround that was suggested to me when I tried to implement this behavior for beat (I wanted to make (beat) default to getting the current beat of *clock*) was to do something like this:

(defun beat (&optional object)
  (if object
      (%beat object)
      (slot-value *clock* 'beat)))

(defmethod %beat ((this pstream))
  ...)

...And then define the methods on %beat instead. However, I don't really like this since I want the generic functions to be something that users of the library can easily add their own methods to. Having %beat be the actual name of the generic function and beat just a regular function would be confusing and feels wrong to me.

I definitely agree with you in principle, though; it would be nice to have (stop) work similarly to how it does with cl-collider. Maybe instead something like (stop t) could be used to stop all patterns? Would that be a good compromise?

@TatriX
Copy link
Author

TatriX commented Mar 2, 2021

(stop t) sounds good enough!

One more question. I'm trying to use (parp) and struggling a bit to understand how to use it.
I figured out that it's very important for :parp pattern not to be infinite .

(pb :pattern
    :degree (pseq (list (list 0 5 10)
                        (list 3 10 14)
                        (list 3 10 14)))
    :parp (pbind :dur (pseq (list 1 1/4 1/2 1/4) 1)))

This will play every chord four times with provided durations. Can I somehow play individual notes with the provided duration? In general having a musical example in the docscrting would be really nice!

BTW, is there anything like Rest? So one could write (pbind :dur (pseq (list 1 (prest 1/4) 1/2 1/4) 1)).

Thank you for all the explanations!

@defaultxr
Copy link
Owner

defaultxr commented Mar 2, 2021

Actually, I just remembered that stop is also implemented for lists, so you can do something like (stop (playing-pdefs)) and it will stop all the pdefs that are playing. Similarly, (stop (clock-tasks)) would stop all the tasks running on *clock*. I will probably still implement (stop t) but I think it'd make more sense for it to stop everything, including any nodes/synths running in the audio backend as well. Does that still sound good?

For your parp question, I'm not 100% sure what you mean; do you mean that you want something like "make degree 0 last 1 beat, degree 5 last 1/4 beat, and degree 10 last 1/2 beat" or similar? If that's what you're going for, I think you can do this:

(pb :pattern
  :degree (pseq (list (list 0 5 10)
                      (list 3 10 14)
                      (list 3 10 14))
                1)
  :sustain (list 1 1/4 1/2)
  :dur 2)

No parp needed in that case. parp is more for if you want to turn each event of the :pattern pattern into multiple events.

If you want the note lengths to "cycle", i.e. if the durations of the notes in the second list in your pseq should be this:

  • degree 3 = 1/4 dur
  • degree 10 = 1 dur
  • degree 14 = 1/4 dur

then I think you'd want to use pclump instead, which "clumps" an input pattern into lists of a specified length (in this case 3):

(pb :pattern
  :degree (pseq (list (list 0 5 10)
                      (list 3 10 14)
                      (list 3 10 14))
                1)
  :sustain (pclump (pseq (list 1 1/4 1/2 1/4)) 3)
  :dur 2)

There's also paclump, "automatic clump" which automatically detects what length the "clump" should be based on the length of the key with the longest list in the input event. That way you don't have to have the chords always have the same number of notes:

(pb :pattern
  :degree (pseq (list (list 0 5 10)
                      (list 3 10 14)
                      (list 3 10 14 18)) ;; not the same length? no problem!
                1)
  :sustain (paclump (pseq (list 1 1/4 1/2 1/4)))
  :dur 2)

Note that (right now at least) you can't have dur or delta keys that have lists as values since those keys are used to determine when the next event(s) of the pattern should occur. Perhaps in the future I'll make lists possible for those and just use the biggest dur in the list to determine when the next event(s) should occur. But you can already get that behavior right now by doing something like this:

(pb :pattern
  :degree (pseq (list (list 0 5 10)
                      (list 3 10 14)
                      (list 3 10 14))
                1)
  :sustain (paclump (pseq (list 1 1/4 1/2 1/4 1/5)))
  :dur (pf (reduce #'max (e :sustain))))

Hope that helps! If I'm misunderstanding you just let me know :)

Right now rest is semi-implemented, just as a note type (as you've already seen). I was planning on implementing the ability to put :rest or :r in pitch keys of a pattern to turn that particular event into a rest type, but then the problem is that if there is a key further down the pbind that tries to manipulate the pitch value with a math operator, it will cause an error, i.e.

(pbind
  :midinote (pseq (list 50 52 54 :rest))
  :midinote (p+ (pk :midinote) 2))) ;; will cause an error

...But just having a function like your example makes more sense since you can still provide an actual numeric value too (which could probably just default to 1 if left unprovided, so as not to cause the kind of error I mentioned). Thanks for the suggestion! That actually makes things easier and I'll probably implement it like that.

@TatriX
Copy link
Author

TatriX commented Mar 3, 2021

Does that still sound good?

Absolutely!

That actually makes things easier and I'll probably implement it like that.

Nice to hear!

I was thinking about arpeggiator as in regular synthesizer: you hold 3 notes let's say :degree (list 0 3 7)
and instead of playing them simultaneously or with varying length of individual notes, they are played in sequence according to some logic (up, down, up-down, random, ...):

((:type :note :degree 0 :dur 1)
 (:type :note :degree 3 :dur 1)
 (:type :note :degree 7 :dur 1))

I was trying to do this with :parp and I realized that I need a way to pick nth element from a key with the chord. I didn't find a way yet though.

And as always, thank you so much for explanations and ideas! More things to try now :)

@defaultxr
Copy link
Owner

Yeah, "arp" is maybe not the best name for that pattern since it doesn't do exactly the same thing as a typical arpeggiator on a synth does.

Obviously if you just want to play the notes of a chord in order, you can just use pseq for that. For other behaviors rather than just "in order" you might look into the other "list patterns" as listed in patterns.org like prand for random elements, or pwalk or pindex for more easily writing your own behavior. i.e. for going through a list in reverse (down) order you can do this:

(next-n (pindex (list 0 1 2 3) (pseries -1 -1) t) 10)
;; ;=> (3 2 1 0 3 2 1 0 3 2)

...Though, thinking about it now, pindex could be made even more useful if its wrap-p argument accepted a "wrapping type", i.e. so wrap, fold, or clip could be used, instead of just toggling wrapping as it does currently.

For doing the "up-down" behavior, pwalk works:

(next-n (pwalk (list 0 1 2 3) 1 (pseq (list 1 -1))) 10)
;; ;=> (0 1 2 3 2 1 0 1 2 3)

You might also find pslide to produce interesting results if you're experimenting.

There isn't a generalized "classic arpeggiator"-style pattern (yet, at least) since most of those kinds of behaviors are covered by other, more general, patterns in the library already. If you want to switch "arpeggiator modes" at runtime without redefining the pattern, I'm not sure how easy that would be. You could do this to switch between up and down on-the-fly:

(defparameter *dir* 1)

(pb :test
  :dur 1
  :degree (pindex (list 0 3 7) (pseries 0 (pf *dir*)) t))

Then you can just redefine *dir* to -1 to reverse direction, back to 1 to go forward, or move 2 at a time, etc. Unfortunately I don't think there's any easy way to make this particular configuration do the up-down behavior though.

And no problem! Glad you're finding the library useful so far :)

@TatriX
Copy link
Author

TatriX commented Mar 3, 2021

Is there a way to reevaluate pattern right now (or better on the nearest quant) without waiting for it to finish?

UPD: using plain list for arpeggiating is indeed a very simple and effective idea, thanks for pointing in the right direction!

    :amp (pwrand [0.7 0.2] [4 1])
    :degree (let ((chords (flatten-1 (list [[0 7 12] 5 10 15]
                                           [-2 5 10 12]
                                           [0 5 10 15]
                                           [7 10 12 5]))))
              (ptrace (pwalk chords (pr (pseq [1 3 5]) (length chords)))))
    :dur (p* (pseq (normalized-sum [5 1 1 1])) 2)

@defaultxr
Copy link
Owner

Technically, patterns are supposed to swap out (or end) at the quant if it's provided, but that is another nonfunctional thing you've stumbled upon, it seems :( I'll try to get that sorted out soon. In the meantime, I guess you could use this helper function:

(defun reset (pattern)
  (mapc #'cl-patterns::clock-remove (pattern-tasks pattern))
  (play pattern))

Then you can just wrap your patterns in (reset ...) like this:

(reset
 (pb :test
   :dur 1
   :degree (pn (pseries 0 1 8))))

That will automatically stop and restart the pattern whenever it's redefined. Not as musical as if it would do that on the quant, though...

You'll also need to be on the latest master for that reset function to work since the most recent commit fixed a bug in the pattern-tasks function.

Glad that arpeggiating with that method does what you were looking for! :)

Also, I tried writing prest but it looks like it would interact poorly with how multichannel expansion is currently done, so I may have to dig into that a bit more before I have an implementation worth publishing.

@TatriX
Copy link
Author

TatriX commented Mar 4, 2021

No stress!
In the meantime here is a snippet of code I quickly hacked recently. Perhaps it could be useful in alsa-midi backend if polished:

(defparameter *midi-input-channel* (1- 1))
(defparameter *midi-chord* nil)
(defparameter *midi-on-notes* nil)

(defun midi-map (messages)
  (dolist (message messages)
    (let* ((event-type (getf message :event-type))
           (event-data (getf message :event-data))
           (source (car (getf message :source)))
           (destination (car (getf message :dest))))
      (declare (ignorable source destination))
      ;; replace `nil' with `t' to see debug output
      (format nil "~a: ~s~%"
              (case event-type
                (:snd_seq_event_noteon (let ((midinote (getf event-data 'cl-alsaseq:note))
                                             (channel (getf event-data 'cl-alsaseq:channel)))
                                         (when (= channel *midi-input-channel*)
                                           (when (emptyp *midi-on-notes*)
                                             (setf *midi-chord* nil))
                                           (pushnew midinote *midi-chord*)
                                           (pushnew midinote *midi-on-notes*))
                                         "Note on"))
                (:snd_seq_event_noteoff (let ((midinote (getf event-data 'cl-alsaseq:note))
                                              (channel (getf event-data 'cl-alsaseq:channel)))
                                          (when (= channel *midi-input-channel*)
                                            (setf *midi-on-notes* (delete midinote *midi-on-notes*)))
                                          "Note off"))
                (:snd_seq_event_controller "CC")
                (t event-type))
              event-data))))
              
(midihelper:stop-midihelper)
(midihelper:start-midihelper :master 96 'midi-map)              

It works similar to chord learn functionality on my hardware sequencer:
You can press any notes on your midi keyboard and the list with distinct midinotes will be stored in *midi-chord*. Pressing any note after all previous notes were released clears previously saved notes and start learning again.

Finally you can use the following small function in sly-mode to insert saved notes at point:

(defun my-insert-chord ()
    "Insert `*midi-chord*' at point"
    (interactive)
    (insert (prin1-to-string (sly-eval 'cl-patterns::*midi-chord*))))

@defaultxr
Copy link
Owner

defaultxr commented Mar 9, 2021

Oh, I see! I don't have a Pyramid myself but it sounds like it's basically like the "latch" functionality that some synthesizers have which keeps track of which keys you've pressed together and holds them until you release them all and start pressing another "chord". I'd definitely like to have some kind of functionality like that in the library, but I'd prefer to keep backend-specific functionality to a minimum if possible. Ideally in the future "latching" like that could be implemented as a pattern type and MIDI (or other data stream types) could just be converted to a cl-patterns event and forwarded to an instance of said pattern. That way, all MIDI or other input streams could be processed with arbitrary patterns too.

Unfortunately right now cl-patterns only supports note type events and not note-on and note-off which are what would be needed to support processing MIDI data through patterns in that way. But it's a good idea though, thanks!

Your elisp function also reminds me that I should cleanup/package my own cl-collider/cl-patterns elisp convenience functions and put them in this repo or something, since I have a few that are handy when working with these libraries. For example:

(defun get-current-paragraph (&optional between-newlines) ;; helper function
  "Gets the buffer positions of the start & end of the current paragraph or defun, or returns the region if it is active.

With between-newlines true, always return buffer positions of the previous and next double-newlines."
  (save-excursion
    (if (region-active-p)
        (cons (region-beginning) (region-end))
      (or (and (not between-newlines) (bounds-of-thing-at-point 'defun))
          (let* ((start-loc (save-excursion (search-backward (string ?\n ?\n) nil t)))
                 (end-loc (search-forward (string ?\n ?\n) nil t))
                 (start (if start-loc
                            (+ start-loc 2)
                          (point-min)))
                 (end (if end-loc
                          (- end-loc 2)
                        (point-max))))
            (cons start end))))))

(defun sly-cl-collider-context ()
  "Get the type (i.e., pdef, ndef, defsynth, proxy, etc) and name of the current context."
  (let* ((bounds (or (bounds-of-thing-at-point 'defun)
                     (get-current-paragraph)))
         (def-string (buffer-substring-no-properties (car bounds) (cdr bounds)))
         (type))
    (save-excursion
      (save-restriction
        (narrow-to-region (car bounds) (cdr bounds))
        (goto-char (point-min))
        (search-forward-regexp "\(\\(\\b\\w+\\b\\)" nil t)
        (setf type (match-string-no-properties 1))
        (goto-char (point-min))
        (search-forward-regexp "\[\(:\]\\(pdef\\|pb\\|defsynth\\|ds\\|proxy\\|ndef\\|dn\\|bdef\\)\[ \t\n\]+:\\(\[^ \t\n\]+\\)" nil t)
        (list (ignore-errors (intern type)) (match-string-no-properties 2))))))

(defun sly-cl-collider-get-name-of-previous-item (items)
  (save-excursion
    (let ((context (sly-cl-collider-context)))
      (beginning-of-defun)
      (while (and (not (member (car context) items))
                  (not (= (point) (point-min))))
        (backward-sexp)
        (setf context (sly-cl-collider-context)))
      (cadr context))))

(defun cl-collider-guess-synth ()
  (sly-cl-collider-get-name-of-previous-item (list 'defsynth 'ds 'proxy 'ndef 'dn)))

(defun sly-cl-collider-select-synth ()
  "Select a synth from the list of currently-defined synthdefs."
  (interactive)
  (let ((synths (sly-eval `(cl:mapcar (cl:lambda (x) (cl:string-downcase (cl:symbol-name x)))
                                      (cl-patterns::keys cl-collider::*synthdef-metadata*))))
        (guess (sly-cl-collider-guess-synth)))
    (completing-read "Synth? " synths nil nil (when (member guess synths) guess))))

(defun sly-play-context (&optional stop)
  "Play or end the pdef, synthdef, bdef, etc, that the point resides within."
  (interactive)
  (cl-destructuring-bind (type name) (sly-cl-collider-context)
    (save-excursion
      (save-restriction
        (narrow-to-region (save-excursion (beginning-of-defun) (point)) (save-excursion (end-of-defun) (point)))
        (cond
         ((member type (list 'pdef 'pb 'pbind))
          (let ((string (concat "(cl-patterns::" (if stop "play-or-stop" "play-or-end") " :" name ")")))
            (sly-interactive-eval string)))
         ((member type (list 'ds 'defsynth 'defsynth*))
          (sly-interactive-eval
           (concat "(play (event :instrument :" name " :dur 2 :amp 1 :latency 0 :quant 0))")))
         ((member type (list 'bdef))
          (let ((string (concat "(play (bdef :" name "))")))
            (sly-interactive-eval string))))))))

With the above code in the sly section of my init.el, I bind C-c p in sly-mode-map to sly-play-context. Then if your point is inside a pdef, pb, or pbind, pressing C-c p will play or end it. That way you don't have to manually type and evaluate (play (pdef :blah)) each time. It can also be used to preview bdefs and synthdefs. It's supposed to work with proxys too but that part depends on some code that I haven't yet cleaned up/published.

I haven't tested that that code will work though; they might depend on some other helper functions I forgot to include or something. If you decide to try them out, let me know if you get any errors or anything.

I've been going through cl-patterns a bit, working on updating the quant code. My plan is to split quant out into a play-quant which determines when the pattern is allowed to start playing, and an end-quant which determines when a pattern is allowed to end or when a redefined pdef can be swapped to its new definition. Setting quant will set both play-quant and end-quant at the same time but they can also both be set individually of course. end-quant will default to nil which will mean that it will have the current behavior, where patterns only stop playing or swap to new definitions when they end (run out of events).

@defaultxr
Copy link
Owner

Hi,

I just pushed a few updates to the library including the changes to the clock/quant handling that I mentioned in my last reply. It ended up being a big restructuring of the clock so there might be some bugs yet (though I've of course tested the changes a bit, and added a few tests as well). See commit ed10b6f for the clock/quant changes. If you try it out I'd be happy to know if you find any bugs!

@TatriX
Copy link
Author

TatriX commented Mar 17, 2021

Nice!

I have a code-style related question. I often use sly-describe-symbol (bound to C-c C-d C-d by default) to read the docs or I just goto definition and read the docs alongside the code. I'm not entirely sure about conventions in Common Lisp, but in Emacs Lisp you essentially use fill-paragraph do format the docs.
So instead of

and

you get

and

@defaultxr
Copy link
Owner

defaultxr commented Mar 17, 2021

I'm not sure if there is an established convention for what column to wrap docstrings/code at in the CL community, but for me C-c C-d C-d on pb shows up like this in Emacs:

2021-03-17-171242_960x223

I thought this was because of visual-line-mode but apparently I don't have it enabled, so I'm not sure why it's different for you. Maybe you have truncate-lines enabled? I think that can be toggled with toggle-truncate-lines if that's not something you intended/wanted. I'm also using Emacs 28.0.50 which may have changed some default compared to the version you're using.

In any case I prefer not to manually split up the paragraphs in docstrings into separate lines since I feel like that's something that should be handled by the program displaying them. Also because I'm working on a cl-patterns-based DAW which will display the docstrings and depending on the size of the window/pane they might end up looking weird, i.e. if the pane is smaller than the length of a line, or if the docstrings are displayed in a non-monospaced font.

@TatriX
Copy link
Author

TatriX commented Mar 19, 2021

My first impression from the new quant is super positive. Now patterns update themselves much more predictably. Thank you so much!

@TatriX
Copy link
Author

TatriX commented Mar 21, 2021

It seems there is something weird happening with the combination of :loop-p and quant:

(progn
 (pb :foo
   :instrument :default
   :loop-p nil
   :quant 1
   :dur 1
   :midinote (pseq [60] 4))
 (play :foo))

This pattern plays only 1 event, but I was expecting 4.

@TatriX TatriX changed the title FAQ Forum Thread Mar 21, 2021
@defaultxr
Copy link
Owner

Since quant sets both the play-quant and the end-quant, it's normal that :quant 1 :loop-p nil results in only one event before the pattern ends. You could either use :quant 4 instead to make it play 4 notes, or :play-quant 1 instead to make it start at any beat divisible by 1 and stop when the pattern normally ends (i.e. play the full pattern once).

@defaultxr
Copy link
Owner

As of de8a449, (stop t) is now implemented to stop all playing pdefs and nodes, on all backends.

@TatriX
Copy link
Author

TatriX commented Mar 29, 2021

(stop t) is very useful!

Two naming questions:
I think MIDI :channel in sclang (and in my head) is just :chan. It makes it nicer to type and look to my taste:

(pb :automatic-jazz
  :type :midi
  :chan (1- 3)
  :note (pshuf (scale-notes :minor) 4)
  :octave (pr (pwhite 2 7))
  :root (pr (pwhite 0 12))
  :dur (pshuf (list 1/3 1/4)))

And then really not important, but still what do you think about :loop-p vs just :loop

@defaultxr
Copy link
Owner

Oh, sorry, I forgot to reply to this. Personally I think :channel is a bit more intuitive, especially since :instrument is a much longer word but is still spelled out. Maybe :chan could be an alias though since I doubt it's likely to be used for anything else in MIDI-based backends.

I prefer :loop-p since it's the same name as the loop-p function and slot. It's also closer to CL conventions since it's a "predicate function". Plus, I could see :loop being a parameter that users might want to have in their synthdefs, so having it as :loop-p reduces the chance of that kind of naming collision.

@defaultxr
Copy link
Owner

As of commit 889e737 the :chan shorthand for the alsa-midi backend is implemented :)

@TatriX
Copy link
Author

TatriX commented Apr 12, 2021

(prest) works like a charm! Thank you as always!

I find myself doing this all the time:

(progn
  (pb :progression
    :backend :alsa-midi
    :quant 1
    :chan (1- 9)
    :scale :chromatic
    :dur (pseq (cl-patterns::normalized-sum [4 2 1]))
    :db (pseq [-3 -5 -8 -10])
    :degree (pwrand [(pseq [(prand [0 -12]) (prand [7 (prest)] 1) (prand [9 10] 1)] 1) (prest)] [3 1])
    :degree (p+ (pk :degree) (pseq [0 -12 -5 0 7])))
  (play :progression))

Perhaps another special key can be added for this specific forkflow:

(pb :progression
  :backend :alsa-midi
  :quant 1
  :state :play
  ;; :state :stop
  ;; :state :pause ; Maybe?
  :chan (1- 9)
  :scale :chromatic
  :dur (pseq (cl-patterns::normalized-sum [4 2 1]))
  :db (pseq [-3 -5 -8 -10])
  :degree (pwrand [(pseq [(prand [0 -12]) (prand [7 (prest)] 1) (prand [9 10] 1)] 1) (prest)] [3 1])
  :degree (p+ (pk :degree) (pseq [0 -12 -5 0 7])))

Key itself and possible values are for illustrative purposes.
When :state is nil, it behaves as it does now.

@defaultxr
Copy link
Owner

One problem with the idea of such a key is that not all patterns are named; i.e. if I do (pbind :state :play :foo 1) it'd work fine, but then if I evaluated (pbind :state :stop :foo 2) it would technically be a different object and there wouldn't really be any easy way to determine with certainty which pattern on the clock should stop playing.

It's easier for pdefs and pbs since they have names that can be used to determine which clock tasks are playing patterns with the same name, but I don't really like the idea of a special key that only works "sometimes".

I'll think about this more though; maybe there is some way to make a key along those lines work for all patterns somehow. I think if I did implement it, I'd probably call the key :playing-p, to be consistent with the name of the playing-p slot/accessor.

On a similar note, I just pushed a few commits, the most recent of which includes an Emacs "helper library" of functions I wrote to make playing/stopping patterns easier, among other things. See this section that I just added to the readme. If you put that setup code in your init.el then you can do C-c p to play-or-end any pdef or pb under the point. It also works with bdefs and cl-collider proxys. I find it really convenient personally since I can just do C-c C-c C-c p to define and start playing a pdef, then to end it I can do C-c p on it again. That way I don't have to waste my time typing out (play :foo), (stop :foo), etc at all :)

@TatriX
Copy link
Author

TatriX commented Apr 15, 2021

Nice! Will give it a try.

It seems this is doing what I need:

(play (pdef :foo
            (pbind
             :quant 1
             :degree (ptrace (pseries 0 1)))))
;; replace play with stop
(stop (pdef :foo
            (pbind
             :quant 1
             :degree (ptrace (pseries 0 1)))))

But this doesn't work:

;; Plays only one one event
(play (pb :foo
        :quant 1
        :degree (ptrace (pseries 0 1))))

@defaultxr
Copy link
Owner

Ohh, that is confusing. Turns out it's because pb returns a pbind object even though it also defines a pdef. pdefs loop-p by default but pbinds don't, and :quant 1 sets both play-quant and end-quant, thus making play on the pbind only output one beat worth of events. One solution to this might be to just to make pb an actual class of its own that subclasses pdef and pbind, rather than having pb simply return a pdef or pbind. Or maybe it should just always return the pdef. I'll look into this and see what the best solution would be.

@defaultxr
Copy link
Owner

OK, I ended up just making pb always return a pdef object. So that should fix that bug.

@TatriX
Copy link
Author

TatriX commented May 14, 2021

What do you think about closing this thread and using Github Discussions instead?

Repository owner locked and limited conversation to collaborators May 14, 2021

This issue was moved to a discussion.

You can continue the conversation there. Go to discussion →

Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants