[Feature Request] Add `Rest`
TatriX opened this issue · comments
Hi! It would be nice to have something similar to Rest from sclang.
I want to dig a bit into the internals of the library so I'll maybe try adding this feature myself.
See also #14 (comment)
I haven't gotten to look into it since the last time I tried implementing prest
but I think the clock changes I made in the process of implementing play-quant
/end-quant
should make it easier to implement prest
"the right way" (or at least, the way that feels right to me). Still probably need to do some restructuring of the way multichannel expansion works, though. In the meantime, I think something like this could work as a stopgap solution to embed rests in arbitrary pattern slots:
(pb :test
:instrument :default
:degree (pseq '((0 2) (1 3) (:rest 4) (3 :rest) 0 :rest 2 :rest) 1)
:embed (pf (let* ((key :degree)
(val (e key)))
(typecase val
(symbol (event :type :rest key 1))
(number (event :type :note))
(list (event :type (mapcar (lambda (i) (if (symbolp i) :rest :note)) val)
key (mapcar (lambda (i) (if (symbolp i) 1 i)) val))))))
:dur 1)
The :embed
pattern parses the :degree
and changes the event to a rest type if it detects a symbol. This should be usable for most keys, you just have to put it after the key you want to embed rests in, and then change the (key :degree)
so it refers to the name of the key in question. There are definitely some caveats to doing this, though (like how you can't do (prest 3)
to specify the numeric value the rest is replaced with) and it might not work for everything. But once prest
is actually implemented there shouldn't be as many of those sorts of caveats (ideally none).
Oh, I missed the second line of your post when I read it initially, somehow. If you want to try adding it yourself, you're certainly welcome to give it a look! The way I attempted to add it before was by changing the raw-set-event-value
function defined in event.lisp. This is what I tried changing it to:
(defun raw-set-event-value (event key value)
"Set the value of KEY to VALUE in EVENT without running any conversion functions."
(with-slots (event-plist) event
(flet ((handle-prest (item)
(if (typep item 'prest)
(progn
(setf event-plist (plist-set event-plist :type :rest))
(slot-value item 'value))
item)))
(let ((value (if (listp value)
(mapcar #'handle-prest value)
(handle-prest value))))
(setf event-plist (plist-set event-plist (make-keyword key) value))))))
I defined the prest
class in patterns.lisp underneath
(defmethod as-pstream ((item pbind-pstream))
item)
like so:
;;; prest
(defclass prest ()
((value :initarg :value :initform 1)))
(defun prest (&optional (value 1))
"Make a prest object, which, when used in a `pbind' or similar event pattern, turns the current event into a rest and yields VALUE for the key's value.
Note that this is not a pattern; it is just a regular function that returns an object.
Example:
;; (next-upto-n (pbind :degree (pseq (list 0 1 (prest 2) 3) 1)))
;; ;=> ((EVENT :DEGREE 0) (EVENT :DEGREE 1) (EVENT :TYPE :REST :DEGREE 2) (EVENT :DEGREE 3))
See also: `pbind', `pbind''s :type key"
(make-instance 'prest :value value))
(defmethod rest-p ((prest prest))
t)
But there is definitely more restructuring that would be needed than just those two things. In particular the multichannel expansion; it will likely need to be moved either into pbind's next
method or better yet an :around next
method for pattern
or the like (so it applies to all patterns automatically without needing to be manually implemented for each, ideally)... At least, I think. Right now it's done in the clock-process
method with the code (split-event-by-lists (event-with-raw-timing event task))
.
The code should be able to pass these tests if prest
is implemented properly:
(test prest
"Test `prest'"
(is (equal (list nil t nil)
(mapcar #'rest-p (next-upto-n
(pbind :dur (pseq (list 1 (prest 2) 1) 1)
:dur (p+ (pk :dur) 1)))))
"prest does not work properly as a :dur value")
(is (equal (list 2 3 2)
(mapcar #'dur (next-upto-n
(pbind :dur (pseq (list 1 (prest 2) 1) 1)
:dur (p+ (pk :dur) 1)))))
"prest's value is not used as the value of the key it is used in")
(is (equal (list nil t nil)
(mapcar #'rest-p (next (pbind :freq (list 1 (prest 2) 1)
:freq (p+ (pk :freq) 1)))))
"prest does not work properly with multichannel expansion")
(is-true (rest-p (prest))
"`rest-p' is not true for prest"))
Good luck if you do decide to give it a try, and let me know if you have any questions. :) Otherwise I will probably get time to implement it soon.