Mercurial > hg > amuse
diff utils/midi-output.lisp @ 33:d1010755f507
Large upload of local changes. Many additions, such as harmony and piece-level objects
darcs-hash:20070413100909-f76cc-a8aa8dfc07f438dc0c1a7c45cee7ace2ecc1e6a5.gz
author | David Lewis <d.lewis@gold.ac.uk> |
---|---|
date | Fri, 13 Apr 2007 11:09:09 +0100 |
parents | |
children | 1d757c33e00e |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/utils/midi-output.lisp Fri Apr 13 11:09:09 2007 +0100 @@ -0,0 +1,155 @@ +;; Make midifiles from basic amuse objects methods here can be +;; overridden for more specific types +;; + +;; FIXME: Need to push some structures from geerdes to make this work. + +(in-package #:amuse-utils) + +(defgeneric play (music) + (:method (m) (play-midifile (make-midi m)))) +(defmethod play ((music composition)) + (play-midifile (make-midi music))) + +(defun play-midifile (midifile) + ;; coremidi is easy as an alternative, but we'll probably want midi + ;; file export anyway, so it makes some sense to focus our efforts + ;; on this first. That said, is there a CoreAudio midi file player + ;; routine? + (midi:write-midi-file midifile "tmp.mid") + #+darwin + (when (sb-impl::find-executable-in-search-path "open") + (asdf:run-shell-command "open tmp.mid") + (return-from play-midifile T)) + (when (sb-impl::find-executable-in-search-path "timidity") + (asdf:run-shell-command "timidity tmp.mid") + (return-from play-midifile T))) + +(defgeneric make-midi (sequence)) +(defmethod make-midi ((sequence sequence)) + ;; Make a midifile object. Collects global midi messages (that + ;; require a sequence) and event-based messages (that don't). + ;; FIXME: Something about this strikes me as very stupid. Must + ;; revisit + ;; FIXME: Only making type 0. Is this a problem? + (let* ((events (event-sequence-messages sequence)) + (globals (global-messages sequence)) + (patches (patch-messages sequence))) + (make-midifile-from-messages (nconc events globals patches) + :type 0))) + +(defun make-midifile-from-messages (events &key (type 0)) + ;; FIXME: clearly broken if type 1 + ;; First have to get rid of all fractional times and choose a + ;; timebase + (let* ((timebase (apply #'lcm (mapcar #'(lambda (x) + (denominator + (midi:message-time x))) + events)))) + (when (< timebase 4) + (setf timebase (* 4 timebase))) + (loop for e in events + do (setf (midi:message-time e) (* timebase + (midi:message-time e)))) + (make-instance 'midi:midifile + :format type + :division timebase + :tracks (list (sort-midi-messages-for-output events))))) + +(defun sort-midi-messages-for-output (messages) + (sort messages #'(lambda (x y) (or (< (midi:message-time x) + (midi:message-time y)) + (and (= (midi:message-time x) + (midi:message-time y)) + (> (midi::message-status x) + (midi::message-status y))))))) + +(defun event-sequence-messages (sequence) + (let ((midinotes)) + (sequence:dosequence (event sequence midinotes) + (let ((messages (event-messages event))) + (dolist (message messages) + (push message midinotes)))))) + +(defun patch-messages (sequence) + (let ((patches (make-array 16 :initial-element nil)) + (patch-list) + (channel 0) + (patch 0)) + (sequence:dosequence (event sequence patch-list) + (setf channel (get-channel-for-midi event) + patch (get-patch-for-midi event)) + (when (or (not (aref patches channel)) + (not (= (aref patches channel) + patch))) + (push (make-instance 'midi:program-change-message + :program patch + :time (timepoint event) + :status (+ channel 192)) + patch-list) + (setf (aref patches channel) patch))))) + + +(defgeneric global-messages (sequence) + (:method (s) (declare (ignore s)) nil)) +(defmethod global-messages ((sequence composition)) + ;; FIXME: missing plenty of other messages + ;; FIXME: messy + (let ((tempi (tempi sequence)) + (temp) + (time-sigs (time-signatures sequence)) + (events)) + (dolist (tempo tempi) + (setf temp (tempo-message tempo)) + (when temp + (push temp events))) + (dolist (time-sig time-sigs events) + (setf temp (time-sig-message time-sig)) + (when temp + (push temp events))))) + +(defgeneric tempo-message (tempo) + (:method (tp) + (progn + (let ((speed (make-instance 'midi:tempo-message + :time (timepoint tp) + :status 255))) + (setf (slot-value speed 'midi::tempo) (microseconds-per-crotchet tp)) + speed)))) + +(defgeneric time-sig-message (time-sig) + (:method (ts) (declare (ignore ts)) nil)) + +(defgeneric event-messages (event) + (:method (e) (declare (ignore e)) nil)) +(defmethod event-messages ((event pitched-event)) + (list (make-instance 'midi:note-on-message + :status (+ (get-channel-for-midi event) 144) + :key (midi-pitch-number event) + :velocity (get-velocity-for-midi event) + :time (timepoint event)) + (make-instance 'midi:note-off-message + :status (+ (get-channel-for-midi event) 128) + :key (midi-pitch-number event) + :velocity (get-velocity-for-midi event) + :time (timepoint (cut-off event))))) + +(defmethod event-messages ((event percussive-event)) + (list (make-instance 'midi:note-on-message + :status 153 + :key (get-pitch-for-midi event) + :velocity (get-velocity-for-midi event) + :time (timepoint event)) + (make-instance 'midi:note-off-message + :status 137 + :key (get-pitch-for-midi event) + :velocity (get-velocity-for-midi event) + :time (timepoint (cut-off event))))) + +(defgeneric get-pitch-for-midi (event)) +(defgeneric get-velocity-for-midi (event) + (:method (e) (declare (ignore e)) 100)) +(defgeneric get-patch-for-midi (event) + (:method (e) (declare (ignore e)) 0)) +(defgeneric get-channel-for-midi (event) + (:method (e) (declare (ignore e)) 0))