You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
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:
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!
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:
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"?
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)...
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:
...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?
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 .
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)).
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:
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 (list0510)
(list31014)
(list3101418)) ;; not the same length? no problem!1)
:sustain (paclump (pseq (list11/41/21/4)))
:dur2)
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:
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 (list505254: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.
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, ...):
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:
...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.
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:
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 :)
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:
Then you can just wrap your patterns in (reset ...) like this:
(reset
(pb :test:dur1:degree (pn (pseries 018))))
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.
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:
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:
(defunget-current-paragraph (&optionalbetween-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) nilt)))
(end-loc (search-forward (string?\n?\n) nilt))
(start (if start-loc
(+ start-loc 2)
(point-min)))
(end (if end-loc
(- end-loc 2)
(point-max))))
(cons start end))))))
(defunsly-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\\)"nilt)
(setf type (match-string-no-properties1))
(goto-char (point-min))
(search-forward-regexp"\[\(:\]\\(pdef\\|pb\\|defsynth\\|ds\\|proxy\\|ndef\\|dn\\|bdef\\)\[\t\n\]+:\\(\[^ \t\n\]+\\)"nilt)
(list (ignore-errors (intern type)) (match-string-no-properties2))))))
(defunsly-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))))
(defuncl-collider-guess-synth ()
(sly-cl-collider-get-name-of-previous-item (list'defsynth'ds'proxy'ndef'dn)))
(defunsly-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 nilnil (when (member guess synths) guess))))
(defunsly-play-context (&optionalstop)
"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).
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!
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).
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.
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 :)
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.
Activity
defaultxr commentedon Feb 23, 2021
Hi!
Yes, for
pbjorklund, the events it outputs have the sum of theirdurs 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&keyargument or the like; that'd be much more convenient than having to do the whole:dur (p* (pk :dur) ...)song-and-dance.pmetais 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
pmetaand specifically its:stretchkey:In this example,
inneris just a basic pattern of four events.outeris the pmeta and if you play it, you'll first hear theinnerplayed 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 theinnerpattern. You could also do this for the same effect:The only problem with the above is that the
4in(pr ... 4)would need to always be set as the same length as theinnerpattern is, whereas withpmetait always applies each "step" to a full playthrough ofinnerregardless 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 thepbindinside 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!
improve pbjorklund: add DUR (as per #14) and REPEATS parameters
TatriX commentedon Mar 1, 2021
New
pbjorklundis lovely, thank you! The only problem with it right now is that it doesn't work very well with:type :midiwhen:embed:Now if I want to send it via MIDI:
Notes are played every beat, as
:typeis overwritten.defaultxr commentedon Mar 1, 2021
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:TatriX commentedon Mar 2, 2021
As I'm still learning I often find that I want to stop everything, so I'm using:
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 commentedon 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:
You'll get the following behavior:
The last definition of
foois 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:...And then define the methods on
%beatinstead. 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%beatbe the actual name of the generic function andbeatjust 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 commentedon 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
:parppattern not to be infinite .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 commentedon Mar 2, 2021
Actually, I just remembered that
stopis 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:
No
parpneeded in that case.parpis more for if you want to turn each event of the:patternpattern 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
pseqshould be this:then I think you'd want to use
pclumpinstead, which "clumps" an input pattern into lists of a specified length (in this case 3):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:Note that (right now at least) you can't have
durordeltakeys 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 biggestdurin 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: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
:restor:rin 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 thepbindthat tries to manipulate the pitch value with a math operator, it will cause an error, i.e....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
1if 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 commentedon Mar 3, 2021
Absolutely!
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, ...):
I was trying to do this with
:parpand 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 commentedon Mar 3, 2021
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
pseqfor that. For other behaviors rather than just "in order" you might look into the other "list patterns" as listed in patterns.org likeprandfor random elements, orpwalkorpindexfor more easily writing your own behavior. i.e. for going through a list in reverse (down) order you can do this:...Though, thinking about it now,
pindexcould be made even more useful if itswrap-pargument accepted a "wrapping type", i.e. sowrap,fold, orclipcould be used, instead of just toggling wrapping as it does currently.For doing the "up-down" behavior,
pwalkworks:You might also find
pslideto 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:
Then you can just redefine
*dir*to-1to reverse direction, back to1to 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 commentedon 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!
defaultxr commentedon Mar 4, 2021
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:Then you can just wrap your patterns in
(reset ...)like this: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
resetfunction to work since the most recent commit fixed a bug in thepattern-tasksfunction.Glad that arpeggiating with that method does what you were looking for! :)
Also, I tried writing
prestbut 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 commentedon 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-midibackend if polished: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-modeto insert saved notes at point:defaultxr commentedon 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
eventand 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
notetype events and notnote-onandnote-offwhich 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:
With the above code in the sly section of my init.el, I bind
C-c pinsly-mode-maptosly-play-context. Then if your point is inside apdef,pb, orpbind, pressingC-c pwill 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 previewbdefs andsynthdefs. It's supposed to work withproxys 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
quantcode. My plan is to splitquantout into aplay-quantwhich determines when the pattern is allowed to start playing, and anend-quantwhich determines when a pattern is allowed toendor when a redefinedpdefcan be swapped to its new definition. Settingquantwill set bothplay-quantandend-quantat the same time but they can also both be set individually of course.end-quantwill default tonilwhich 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 commentedon Mar 17, 2021
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!
3 remaining items
TatriX commentedon Mar 21, 2021
It seems there is something weird happening with the combination of
:loop-pandquant:This pattern plays only 1 event, but I was expecting 4.
[-]FAQ[/-][+]Forum Thread[/+]defaultxr commentedon Mar 21, 2021
Since
quantsets both theplay-quantand theend-quant, it's normal that:quant 1 :loop-p nilresults in only one event before the pattern ends. You could either use:quant 4instead to make it play 4 notes, or:play-quant 1instead 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 commentedon Mar 26, 2021
As of de8a449,
(stop t)is now implemented to stop all playing pdefs and nodes, on all backends.TatriX commentedon Mar 29, 2021
(stop t)is very useful!Two naming questions:
I think MIDI
:channelin sclang (and in my head) is just:chan. It makes it nicer to type and look to my taste:And then really not important, but still what do you think about
:loop-pvs just:loopdefaultxr commentedon Apr 3, 2021
Oh, sorry, I forgot to reply to this. Personally I think
:channelis a bit more intuitive, especially since:instrumentis a much longer word but is still spelled out. Maybe:chancould be an alias though since I doubt it's likely to be used for anything else in MIDI-based backends.I prefer
:loop-psince it's the same name as theloop-pfunction and slot. It's also closer to CL conventions since it's a "predicate function". Plus, I could see:loopbeing a parameter that users might want to have in their synthdefs, so having it as:loop-preduces the chance of that kind of naming collision.defaultxr commentedon Apr 3, 2021
As of commit 889e737 the
:chanshorthand for the alsa-midi backend is implemented :)TatriX commentedon Apr 12, 2021
(prest)works like a charm! Thank you as always!I find myself doing this all the time:
Perhaps another special key can be added for this specific forkflow:
Key itself and possible values are for illustrative purposes.
When
:stateisnil, it behaves as it does now.defaultxr commentedon Apr 15, 2021
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 andpbs 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 theplaying-pslot/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.elthen you can doC-c ptoplay-or-endanypdeforpbunder the point. It also works withbdefs and cl-colliderproxys. I find it really convenient personally since I can just doC-c C-c C-c pto define and start playing a pdef, then to end it I can doC-c pon it again. That way I don't have to waste my time typing out(play :foo),(stop :foo), etc at all :)TatriX commentedon Apr 15, 2021
Nice! Will give it a try.
It seems this is doing what I need:
But this doesn't work:
defaultxr commentedon Apr 15, 2021
Ohh, that is confusing. Turns out it's because
pbreturns apbindobject even though it also defines apdef.pdefs loop-p by default butpbinds don't, and:quant 1sets bothplay-quantandend-quant, thus makingplayon thepbindonly output one beat worth of events. One solution to this might be to just to makepban actual class of its own that subclassespdefandpbind, rather than havingpbsimply return apdeforpbind. Or maybe it should just always return thepdef. I'll look into this and see what the best solution would be.defaultxr commentedon Apr 17, 2021
OK, I ended up just making
pbalways return apdefobject. So that should fix that bug.TatriX commentedon May 14, 2021
What do you think about closing this thread and using Github Discussions instead?