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

rationals in rhythm-seqs #58

Open
Leon-Focker opened this issue Apr 18, 2022 · 5 comments
Open

rationals in rhythm-seqs #58

Leon-Focker opened this issue Apr 18, 2022 · 5 comments

Comments

@Leon-Focker
Copy link
Collaborator

Leon-Focker commented Apr 18, 2022

There seems to be an issue with rationals as rhythm values when trying to write cmn, lilypond or xml data. Here is a minimal example that produces a weird score:

(let* ((working-title
	(make-slippery-chicken
	 '+working-title+
	 :title "working-title"
	 :ensemble '(((oboe (oboe :midi-channel 1))))
	 :set-palette '(( 1 ((c4))))
	 :tempo-map '((1 (q 90)))
	 :set-map `((1 (1)))
	 :rthm-seq-palette '((1 ((((4 4) 4/3 4)))))
	 :rthm-seq-map `((1 ((oboe (1))))))))
  (cmn-display working-title)
  (lp-display working-title)
  (write-xml working-title))

With cmn, the 4/3, which should be a dotted half note, becomes just a half note.
Lilypond writes a whole note and the following quarter into a second bar, which shoudln't exist.
When opening the xml file with musescore I see a whole note and a quarter in one 4/4 bar.
For me there is no error or warning within sc.

@mdedwards
Copy link
Owner

mdedwards commented Apr 18, 2022 via email

@mdedwards
Copy link
Owner

PS (get-rhythm-letter-for-value 4/3) ---> h.

@Leon-Focker
Copy link
Collaborator Author

Ah I see, I'll try and make acquaintance with rqq then!

The (get-rhythm-letter-for-value) function helps a lot for now. Do you think that some form of it should maybe be run when parsing the rhythms? In that case you could probably prevent some weird notation outputs and also get a warning as soon as rationals get a bit out of hand for notation...

@Leon-Focker
Copy link
Collaborator Author

Leon-Focker commented Apr 24, 2022

Sadly rqq only solves a few of my troubles... Maybe working with a 13/16 time signature wasn't such a good idea after all :d
But since the problems with odd time signatures seem to be known, I'm not going to post them here again.
Instead I have cooked up some functions that attempt to turn a list of rationals into rqq notation. Even though they don't work for what I wanted to do, maybe they can help other slippery chicken users? Especially in a glorious future, where rqq can split 13 into 5 and 8... Currently I'm not sure wheter there are many usecases in which this is more usefull than (get-rythm-letter-for-value) but maybe there are.

;; *** separate-rhythms
;;; some rather 'brute-forcey' way to group and sub-group the durations
;;; by checking how they can be divided. If there is a mathematically elegant
;;; way of doing this, please let me know!
(defun separate-rhythms (rhythms &key (time-sig '(4 4)))
  ;; calculate duration of one bar relative to 4/4
  (let* ((bar-ratio (/ (car time-sig) (cadr time-sig)))
	 (match '()))
    ;; find possible dividing points and save them into match:
    (loop for i from 2 to 20 until match do
	 (loop for j from 1 and r in rhythms sum r into sum do
	      (when (member sum
			    (loop repeat (- i 1) for k from 1
			       collect (* (/ k i)
					  bar-ratio))
			    :test #'=)
		(push j match))))
    ;; if we can devidie the sequence further, do so, else just return it
    (if match
	(progn (push (length rhythms) match)
	       (loop for i in (reverse match) with last = 0 collect
		    (let* ((ls (subseq rhythms last i))
			   (len (loop for i in ls sum i)))
		      ;; maybe divide the subgroups again recursively:
		      (if (or (atom (cdr ls)) (apply #'= ls))
			  ls
			  (separate-rhythms ls
					    :time-sig (list len 1))))
		  do (setf last i)))
	rhythms)))

;; *** ratio-pair-p
;;; checks if list has a certain structure: (1 (1...))
(defun ratio-pair-p (ls)
  (and (atom (car ls)) (listp (second ls)) (not (equal '() (second ls)))))

;; *** list-of-atoms-p
;;; checks if list contains lists
(defun list-of-atoms-p (ls)
  (every #'identity (mapcar #'atom ls)))

;; *** rhythmlist-to-rqq
;;; for a list of durations get rqq notation:
;;; eg.: '(1/8 1/8), time-sig '(1 4) --> (1 ((1 (1)) (1 (1))))
(defun rhythmlist-to-rqq (rhythms &key (time-sig '(4 4)))
  ;; rests will remember which notes were actually rests
  ;; (since for now we will only work with duration-values)
  (let* ((rests (loop for r in rhythms collect (if (atom r) nil t)))
	 ;; divide sequence into subgroups for tuplets:
	 (divisions (separate-rhythms (flatten rhythms) :time-sig time-sig))
	 (result '())
	 (index 0))
    ;; helper is a recursive function to return those nice and nested
    ;; rqq structures. 
    ;; I'm sure this could be more elegant but I cannot be bothered... 
    (labels ((helper (ls)
	       (cond ((list-of-atoms-p ls)
		      (let* ((sum (loop for i in ls sum i))
			     (gcd (gcd-of-list ls))
			     (new-ls (loop for i in ls collect (/ i gcd))))
			(list sum new-ls)))
		     ((every #'identity
			     (mapcar #'ratio-pair-p ls))
		      (let* ((sum (loop for i in ls sum (car i)))
			     (gcd (gcd-of-list (loop for i in ls collect
						    (car i))))
			     (new-ls (loop for i in ls collect
					  (append (list (/ (car i) gcd))
						  (cdr i)))))
		        (list sum new-ls)))
		     (t (let* ((new (loop for i in ls collect (helper i))))
			  (if (ratio-pair-p new)
			      new
			      (helper new))))))
	     ;; this function will replace certain notes with rests
	     ;; by looping through the lists that correspond to actualy notes
	     ;; and comparing with the rests list we created earlier
	     (get-some-rest (ls)
	       (if (listp ls)
		   (if (list-of-atoms-p ls)
		       (loop for i in ls collect
			    (if (nth index rests) (list i) i)
			  do (incf index))
		       (loop for i in ls collect (get-some-rest i)))
		   ls)))
      ;; scale first element according to time-signature:
      (setf result (cons (* (/ (car time-sig) (cadr time-sig)) 4)
			 (cdr (helper divisions))))
      ;; for now we couldn't distinguish between notes and rests so
      ;; we need to replace the respective notes with rests here:
      (get-some-rest result))))

;; using the tuplets from the rqq manual as an example:
(let* ((working-title
	(make-slippery-chicken
	 '+working-title+
	 :title "working-title"
	 :ensemble '(((oboe (oboe :midi-channel 1))))
	 :set-palette '(( 1 ((c4))))
	 :tempo-map '((1 (q 90)))
	 :set-map `((1 (1)))
	 :rthm-seq-palette `((1 ((((1 4) ,(rhythmlist-to-rqq '((1/12) (2/36) 1/36 1/36 1/36 1/36)
							     :time-sig '(1 4)))
				  ((4 4) ,(rhythmlist-to-rqq
					   '(1/45 (1/45) 1/45 1/45 1/45 5/144 5/144 5/144 5/144
					     1/45 (1/45) 1/45 1/45 1/45 5/144 5/144 5/144 5/144
					     1/45 (1/45) 1/45 1/45 1/45 5/144 5/144 5/144 5/144
					     1/45 (1/45) 1/45 1/45 1/45 5/144 5/144 5/144 5/144)))))))
	 :rthm-seq-map `((1 ((oboe (1))))))))
  (cmn-display working-title)
  (lp-display working-title)
  (write-xml working-title :file "/E/test.xml")
  )

@mdedwards
Copy link
Owner

mdedwards commented Oct 11, 2022 via email

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