1 (in-package :gsharp-play
)
3 (defparameter *midi-temp-file
* "/tmp/timidity.mid")
4 (defparameter *midi-player
* "timidity")
5 (defparameter *midi-player-arguments
* '())
10 (defun midi-pitch (note)
11 (round (+ (+ 6900 ; a above middle c, 440 Hz
12 (* 1200 (log (/ (master-pitch-freq *tuning
*) 440) 2)))
13 (- (note-cents note
*tuning
*)
14 (note-cents (master-pitch-note *tuning
*) *tuning
*)))
17 (defun cents-adjustment (note)
18 (nth-value 1 (midi-pitch note
)))
20 (defun measure-durations (slices)
21 (let ((durations (mapcar (lambda (slice)
26 collect
(reduce #'max durations
:key
#'car
)
27 do
(setf durations
(remove nil
(mapcar #'cdr durations
))))))
29 (defun average (list &key
(key #'identity
))
34 (incf sum
(funcall key elem
)))
37 (defun events-from-element (element time channel
)
38 (when (typep element
'cluster
)
40 (make-instance 'pitch-bend-message
42 :status
(+ #xE0 channel
)
43 :value
(+ 8192 ;; middle of pitch-bend controller
45 (* 4096/100 ;; 4096 points per 100 cents
46 ;; midi can only do per-channel pitch bend,
47 ;; not per-note pitch bend, so as a sad
48 ;; compromise we average the pitch bends
49 ;; of all notes in the cluster
50 (average (notes element
)
51 :key
#'cents-adjustment
))))))
52 (mapcar (lambda (note)
53 (make-instance 'note-on-message
55 :status
(+ #x90 channel
)
56 :key
(midi-pitch note
) :velocity
100))
57 (remove-if #'tie-left
(notes element
)))
58 (mapcar (lambda (note)
59 (make-instance 'note-off-message
60 :time
(+ time
(* *tempo
* (duration element
)))
61 :status
(+ #x80 channel
)
62 :key
(midi-pitch note
) :velocity
100))
63 (remove-if #'tie-right
(notes element
))))))
65 (defun events-from-bar (bar time channel
)
66 (mapcan (lambda (element)
67 (prog1 (events-from-element element time channel
)
68 (incf time
(* *tempo
* (duration element
)))))
71 (defun track-from-slice (slice channel durations
&key
(start-time 0))
72 (let ((time start-time
))
73 (cons (make-instance 'program-change-message
74 :time time
:status
(+ #xc0 channel
) :program
0)
75 (mapcan (lambda (bar duration
)
76 (prog1 (events-from-bar bar time channel
)
77 (incf time
(* *tempo
* duration
))))
78 (bars slice
) durations
))))
80 (define-condition midi-player-failed
(gsharp-condition)
81 ((midi-player :initarg
:midi-player
)
82 (exit-code :initarg
:exit-code
))
84 (lambda (condition stream
)
85 (with-slots (midi-player exit-code
) condition
87 "Midi player ~S returned exit code ~S, indicating that an error occurred."
88 midi-player exit-code
)))))
90 (defun play-tracks (tracks)
91 (let ((midifile (make-instance 'midifile
95 (write-midi-file midifile
*midi-temp-file
*)
97 (ext:run-program
*midi-player
*
98 (append *midi-player-arguments
*
99 (list *midi-temp-file
*)))
102 (sb-ext:run-program
*midi-player
*
103 (append *midi-player-arguments
*
104 (list *midi-temp-file
*))
106 (sb-ext:process-wait process
)
107 (when (not (zerop (sb-ext:process-exit-code process
)))
108 (error 'midi-player-failed
109 :midi-player
*midi-player
*
110 :exit-code
(sb-ext:process-exit-code process
))))
112 (ext:run-program
*midi-player
*
113 :arguments
(append *midi-player-arguments
*
114 (list *midi-temp-file
*)))
115 #-
(or cmu sbcl clisp
)
116 (error "write compatibility layer for RUN-PROGRAM")))
118 (defun play-layer (layer)
119 (let* ((slice (body layer
))
120 (durations (measure-durations (list slice
)))
121 (*tempo
* (tempo (segment layer
)))
122 (*tuning
* (gsharp-buffer:tuning
(segment layer
)))
123 (tracks (list (track-from-slice slice
0 durations
))))
124 (play-tracks tracks
)))
126 (defun segment-tracks (segment &key
(start-time 0))
127 (let* ((slices (mapcar #'body
(layers segment
)))
128 (durations (measure-durations slices
))
129 (*tempo
* (tempo segment
))
130 (*tuning
* (gsharp-buffer:tuning segment
)))
134 collect
(track-from-slice slice i durations
:start-time start-time
))
135 (reduce #'+ durations
))))
137 (defun play-segment (segment)
138 (play-tracks (segment-tracks segment
)))
140 ; TODO: There is a short pause between segments?
141 (defun play-buffer (buffer)
143 (num-tracks (loop :for segment
:in
(segments buffer
)
144 :maximize
(length (layers segment
))))
145 (tracks (loop :for i
:from
0 :below num-tracks
:collect nil
)))
147 ; Collect snippets from each segment that should go to different tracks
148 (dolist (segment (segments buffer
))
149 (let ((*tempo
* (tempo segment
))
150 (*tuning
* (tuning segment
)))
151 (multiple-value-bind (track-addendums segment-duration
)
152 (segment-tracks segment
:start-time time
)
153 (format t
"~S" segment-duration
)
155 (incf time segment-duration
)
157 (loop :for track-addendum
:in track-addendums
158 :for tracks-tail
:on tracks
159 :do
(push track-addendum
(car tracks-tail
))))))
161 ; Concatenate each track's snippets
162 (loop :for tracks-tail
:on tracks
163 :do
(setf (car tracks-tail
)
164 (reduce (lambda (result snippet
)
165 (nconc snippet result
))
169 (play-tracks tracks
)))