Jump to content
torstenanders

Polyphonic preview?

Recommended Posts

I really like how Opusmodus allows to preview monophonic snippets (and other material, like interval sequences). No need to explicitly call some function or connect to some editor, as in other composition systems -- just use a keyboard shortcut to see and hear an intermediate result. However, what I miss is notating/auditioning intermediate results of polyphonic music with a single shortcut.  

 

So, I defined the function preview-score, based on def-score. The function expects a polyphonic score in the slightly simplified format we discussed earlier. Here is a minimal example.

 

(preview-score
 '(:vln ((q b4 a4 h g4))
   :vlc ((h g3 b3))))

 

If I want to control further notation or playback parameters, preview-score provides arguments for that, but that is basically the same as using def-score directly. Instead, such parameters can be defined only once with global variables, and then used for multiple calls to preview-score. Here is some example setting.

 

(setf *default-preview-score-instruments*
  '(:vln (:program 'violin :sound 'gm)
    :vlc (:program 'cello :sound 'gm)))

(defparameter *default-preview-score-header*
  '(:title "Opus magnum"
    :tempo 80))

 

Janusz: How can I define a keyboard shortcut that calls the function preview-score with the score returned by a selected code region (or the Lisp expression just before the cursor)? Thanks! 

 

For completeness, the definition of preview-score is below. 

 

Best,

Torsten

 

;;; just some dummy settings for now
(defparameter *default-preview-score-instruments*
  '(:vln (:program 'violin :sound 'gm)
    :vlc (:program 'cello :sound 'gm))
  "Settings for each instrument used by `preview-score'. The format is a plist where keys are the instrument labels, and values a list with the actual settings. For format of these settings are the same as instrument settings for `def-score' with keywords like :sound, :channel etc. -- except for they key :omn.")

;;; just some dummy settings for now
(defparameter *default-preview-score-header*
  '(:title "Opus magnum"
    :tempo 80)
  "Global score settings used by `preview-score'. The format is a plist where keys are the instrument labels, and values a list with the actual settings. The format is the same as the header settings for `def-score' with keywords like :title, :key-signature etc.")

(defun preview-score (score &key (name 'test-score)    
			    (instruments *default-preview-score-instruments*)
			    (header *default-preview-score-header*))
  "Notates and plays a score in a format slightly simpler than expected by def-score, i.e., without its header. 
    
    Args: 
    - score (plist): a headerless score. See below for its format.
    - name (symbol): The score name.
    - instruments (plist): Keys are instrument labels, and values a list with the actual settings. These settings have the same format as instrument settings for `def-score' with keywords like :sound, :channel etc. -- except for they key :omn.
    - header (plist): The format is the same as the header settings for `def-score' with keywords like :title, :composer, :key-signature etc. 
    

    Score format: 
    ;;; (<part1-name-keyword> <part1-OMN>
    ;;;  <part2-name-keyword> <part2-OMN>
    ;;;  ...) 

    
Example:

;;; (preview-score
;;;  '(:vln ((q g4) (q. c5 e d5 q e5 f5) (h. e5))
;;;    :vlc ((q g3) (q c4 b3 a3 g3) (h. c3)))
;;;  :instruments '(:vln (:program 'violin :sound 'gm)
;;; 	            :vlc (:program 'cello :sound 'gm))
;;;  :header '(:title \"Opus magnum\"
;;; 	       :tempo 80))
  "
  ;; Using eval is problematic (https://stackoverflow.com/questions/2571401/why-exactly-is-eval-evil/),
  ;; but hard to avoid for a dynamically created def-score expression that requires splicing with ,@.
  ;; Possible alternative would be to define preview-score as macro, but then arguments are not evaluated. 
 (eval 
   `(def-score ,name 
      ;; quote all header args, because symbol values must be quoted...
      ,(mapcar #'(lambda (x) `',x)					
	       (append header
		       ;; add default vals of required header args at end -- they are overwritten by args given
		       (list :key-signature 'atonal
			     ;; By default, use implicit time signature of 1st part
			     :time-signature (om:get-time-signature (second score))
			     :tempo 70)))
      ,@(mapcar #'(lambda (part)
		    (let ((part-symbol (first part))
			  (part-omn (second part)))
		       (list* part-symbol 
			      :omn `(quote ,part-omn)
			      (getf instruments part-symbol))))
		(plist->pairs score)))
   )
  (audition-musicxml-last-score)
  *last-score*)


#| ; mini test
(preview-score
 '(:vln ((q g4) (q. c5 e d5 q e5 f5) (h. e5))
   :vlc ((q g3) (q c4 b3 a3 g3) (h. c3)))
 :instruments '(:vln (:program 'violin :sound 'gm)
	        :vlc (:program 'cello :sound 'gm))
 :header '(:title "Opus magnum"
	   :tempo 80))
|#
 

 

Share this post


Link to post
Share on other sites

A function is missing:  PLIST->PAIRS 

> Error: There is no package named "TU" .
> While executing: ccl::%parse-token, in process Listener-1(6).
> Type cmd-/ to continue, cmd-. to abort, cmd-\ for a list of available restarts.
> If continued: Retry finding package with name "TU".
> Type :? for other options.

 

Share this post


Link to post
Share on other sites

Sorry, I am using multiple libraries of own functions in the background. Easy to miss one :)

 

EDIT: Sorry, the function one-level-flat was still missing and has been added now. I also removed the package prefix above. 

 

(defun plist->pairs (plist)
  (loop :for (key val) :on plist :by #'cddr
    :collect (list key val)))

; (plist->pairs '(:length 1/16 :pitch 60 :velocity 30))
; -> ((:LENGTH 1/16) (:PITCH 60) (:VELOCITY 30))

(defun pairs->plist (pairs)
  (one-level-flat pairs))

; (pairs->plist '((:LENGTH 1/16) (:PITCH 60) (:VELOCITY 30)))
; -> (:LENGTH 1/16 :PITCH 60 :VELOCITY 30)

(defun one-level-flat (list)
  "flatens one level of the given form.
Example: (one-level-flat '(((note) (note)) ((pause) (pause)) ((note))))
-> ( (note) (note)  (pause) (pause)  (note))"
  (apply #'append list))
  

 

Share this post


Link to post
Share on other sites

Dear Janusz

 

To be clear: I do not expect that there will be a keyboard shortcut for every user added to Opusmodus. Instead, if you just share an example of code how to add a short key (and ideally a menu entry) to the Hemlock editor, that would be great.

 

Thanks again!

 

Best,

Torsten 

Share this post


Link to post
Share on other sites

Dear Janusz,

 

How can I define a keyboard shortcut that calls the function preview-score with the score returned by a selected code region (or the Lisp expression just before the cursor)?

 

In order to make this more easy for you, perhaps you could simply share, e.g., how you defined the shortcut for plotting number lists bound to Ctrl-1?  

 

Thanks! 

 

Best,

Torsten

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now


×