changeset 35:1d757c33e00e

Changes for midi file import darcs-hash:20070502153016-f76cc-89b748c36180ccaca77d2a70a65a6e7f77df8d43.gz
author David Lewis <d.lewis@gold.ac.uk>
date Wed, 02 May 2007 16:30:16 +0100
parents 81b4228e26f5
children ad321ce17e3e
files amuse.asd base/classes.lisp base/package.lisp implementations/midi/midifile-import.lisp utils/midi-output.lisp
diffstat 5 files changed, 145 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/amuse.asd	Wed Apr 18 14:50:09 2007 +0100
+++ b/amuse.asd	Wed May 02 16:30:16 2007 +0100
@@ -32,5 +32,7 @@
                       ((:file "package")
                        (:file "classes" :depends-on ("package"))
                        (:file "constructors" :depends-on ("package" "classes"))
-	               (:file "methods" :depends-on ("package" "classes"))))))))
+	               (:file "methods" :depends-on ("package" "classes"))
+		       (:file "midifile-import"
+		        :depends-on ("package" "classes" "constructors" "methods"))))))))
 
--- a/base/classes.lisp	Wed Apr 18 14:50:09 2007 +0100
+++ b/base/classes.lisp	Wed May 02 16:30:16 2007 +0100
@@ -12,6 +12,7 @@
 
 ;; types of information-specifiers
 
+(defclass identifier () ()) ;; for composition specification
 (defclass moment-designator () ())
 (defclass period-designator () ())
 (defclass anchored-period-designator (moment-designator period-designator) ())
--- a/base/package.lisp	Wed Apr 18 14:50:09 2007 +0100
+++ b/base/package.lisp	Wed May 02 16:30:16 2007 +0100
@@ -3,6 +3,7 @@
   (:export #:constituent
 	   #:composition
 	   #:monody
+	   #:identifier
 	   #:moment-designator
 	   #:period-designator
 	   #:anchored-period-designator
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/implementations/midi/midifile-import.lisp	Wed May 02 16:30:16 2007 +0100
@@ -0,0 +1,139 @@
+(cl:in-package #:amuse-midi) 
+
+(defclass midifile-identifier (identifier)
+  ((pathname :initarg :path
+	     :reader midifile-identifier-pathname
+	     :initform 'nil)))
+
+(defun midifile-id (pathname)
+  (make-instance 'midifile-identifier :path pathname))
+
+(defmethod get-composition ((identifier midifile-identifier))
+  (%initialise-midifile-composition (midi:read-midi-file
+				     (midifile-identifier-pathname identifier))))
+
+(defun %initialise-midifile-composition (midifile)
+  ;; Takes a midifile object (from the "MIDI" package)
+  ;; and returns an amuse midi object
+  ;; FIXME: gets it wrong if patch changes in mid-note
+  ;; FIXME: assumes controllers are global in scope and location
+  (let ((tracks (midi:midifile-tracks midifile))
+	(division (midi:midifile-division midifile))
+	(notes) (time-sigs) (tempi) (misses 0) (track-no -1) (last-time 0))
+    (dolist (track tracks)
+      (incf track-no)
+      (setf track (sort (copy-seq track)
+			#'(lambda (x y)
+			    (or (< (midi:message-time x)
+				   (midi:message-time y))
+				(and (= (midi:message-time x)
+					(midi:message-time y))
+				     (typep x 'midi:note-off-message))))))
+      (let ((ons (make-array '(17 128) :initial-element nil))
+	    (offs) 
+	    (patches (make-array 17 :initial-element 0)))
+	(dolist (event track)
+	  (when (> (midi:message-time event) last-time)
+	    (setf last-time (midi:message-time event)))
+	  (cond
+	    ((or (typep event 'midi:note-off-message)
+		 (and (typep event 'midi:note-on-message)
+		      (= (midi:message-velocity event) 0)))
+	     (let ((pitch (midi:message-key event))
+		   (channel (1+ (midi:message-channel event)))
+		   (t-off (midi:message-time event)))
+	       (if (aref ons channel pitch)
+		   (push (make-event-from-on-off-pair (aref ons channel pitch)
+						      t-off
+						      division
+						      track-no
+						      (aref patches channel))
+			 notes)
+		   ;; if there's no matching on, wait until the beat
+		   ;; is done.
+		   (push event offs))))
+	    ((typep event 'midi:note-on-message)
+	     (let ((pitch (midi:message-key event))
+		   (channel (1+ (midi:message-channel event)))
+		   (t-off (midi:message-time event)))
+	       (when (aref ons channel pitch)
+		 ;; there's a note already sounding. End it.
+		 (push (make-event-from-on-off-pair (aref ons channel pitch)
+						    t-off
+						    division
+						    track-no
+						    (aref patches channel))
+		       notes))
+	       (setf (aref ons channel pitch) event)))
+	    ((typep event 'midi:time-signature-message)
+	     ;; FIXME: Should I make a midi version of this object,
+	     ;; with track/channel?
+	     (when time-sigs
+	       (setf (duration (car time-sigs))
+		     (- (/ (midi:message-time event)
+			   division)
+			(timepoint (car time-sigs)))))
+	     (push (make-instance 'basic-time-signature
+				  :time (/ (midi:message-time event)
+					   division)
+				  :numerator (midi:message-numerator event)
+				  :denominator (midi:message-denominator event))
+		   time-sigs))
+	    ((typep event 'midi:tempo-message)
+	     (when tempi
+	       (setf (duration (car tempi))
+		     (- (/ (midi:message-time event)
+			   division)
+			(timepoint (car tempi)))))
+	     (push (make-instance 'tempo
+				  :time (/ (midi:message-time event)
+					   division)
+				  :bpm (microsecond-per-crotchet-to-bpm (midi:message-tempo event)))
+		   tempi))
+	    ((typep event 'midi:program-change-message)
+	     (setf (aref patches (1+ (midi:message-channel event)))
+		   (midi:message-program event)))
+	    (t (incf misses))))))
+    (when tempi
+      (setf (duration (car tempi)) (- (/ last-time division) (timepoint (car tempi)))))
+    (when time-sigs
+      (setf (duration (car time-sigs)) (- (/ last-time division) (timepoint (car time-sigs)))))
+    ;; make a midi object from notes, etc.
+    (let ((composition (make-instance 'midi-composition
+				      :time 0
+				      :interval (/ last-time division)
+				      :time-signatures (sort time-sigs #'time<)
+				      :tempi (sort tempi #'time<))))
+      (sequence:adjust-sequence composition
+				(length notes)
+				:initial-contents (sort notes #'time<)))))
+
+(defparameter *short* nil)
+
+(defun make-event-from-on-off-pair (note-on cut-off divisions track patch)
+  (when (< (/ (- cut-off (midi:message-time note-on)) divisions) 1/8)
+    (push (cons note-on cut-off) *short*))
+  (cond
+    ((or (= (midi:message-channel note-on) 9)
+	 (> patch 111))
+     ;; percussive
+     (make-instance 'midi-percussive-event
+		    :channel (1+ (midi:message-channel note-on))
+		    :track track
+		    :time (/ (midi:message-time note-on) divisions)
+		    :interval (/ (- cut-off (midi:message-time note-on))
+				 divisions)
+		    :velocity (midi:message-velocity note-on)
+		    :patch patch
+		    :sound (midi:message-key note-on)))
+    (t
+     ;; pitched
+     (make-instance 'midi-pitched-event
+		    :channel (1+ (midi:message-channel note-on))
+		    :track track
+		    :time (/ (midi:message-time note-on) divisions)
+		    :interval (/ (- cut-off (midi:message-time note-on))
+				 divisions)
+		    :velocity (midi:message-velocity note-on)
+		    :patch patch
+		    :number (midi:message-key note-on)))))
\ No newline at end of file
--- a/utils/midi-output.lisp	Wed Apr 18 14:50:09 2007 +0100
+++ b/utils/midi-output.lisp	Wed May 02 16:30:16 2007 +0100
@@ -61,7 +61,7 @@
 					(midi:message-time y))
 				     (and (= (midi:message-time x)
 					     (midi:message-time y))
-					  (> (midi::message-status x)
+					  (< (midi::message-status x)
 					     (midi::message-status y)))))))
 
 (defun event-sequence-messages (sequence)