Projets d'informatique musicale
Licence-Maîtrise d'informatique - Université de Caen
Marc Chemillier
;;; =====================================
;;; Ecriremidifile.lisp / 26 octobre 1998
;;; =====================================
(defun ecrire-midifile (signature tempo liste-couples fich)
(sauve-midifile (conversion-midifile signature tempo liste-couples) fich))
(defun sauve-midifile (liste-octets fich)
(delete-file fich)
(with-open-file (midi-stream fich :direction :output :if-does-not-exist :create :if-exists :supersede
:element-type 'unsigned-byte)
(ecrire-stream liste-octets midi-stream)
(set-mac-file-type fich "Midi")))
(defun ecrire-stream (liste-octets flot)
(cond ((null liste-octets) 'stop)
(t (write-byte (car liste-octets) flot)
(ecrire-stream (cdr liste-octets) flot))))
;;; Conversion d'un Midifile:
;;; -------------------------
;;; EXEMPLE: signature = (4 4) ;;; 4/4
;;; tempo = 120 ;;; nbre de noires/60 secondes
;;; liste-couples ( ) = ((60 96) (62 96) (64 192)))
;;; -> do3 "noire" 96 ticks, re3 "noire" 96 ticks, mi3 "blanche" 192 ticks
;;; 1 "tick" = "division" de la pulsation (donnee par le header)
;;; donne:
;;; (#x4D #x54 #x68 #x64 ;;; M T h d
;;; #x00 #x00 #x00 #x06 ;;; longueur = 6 octets
;;; #x00 #x01 #x00 #x04 #x00 #x60 ;;; format=1, 4 tracks, division=96
;;; #x4D #x54 #x72 #x6B ;;; M T r k
;;; #x00 #x00 #x00 #x2C ;;; longueur
;;; #x00 #xFF #x58 #x04 #x04 #x02 #x18 #x08 ;;; signature
;;; #x00 #xFF #x51 #x03 #x07 #xA1 #x20 ;;; tempo
;;; #x00 #x90 #x3C #x3C #x60 #x80 #x3C #x00 ;;; do3
;;; #x00 #x90 #x3E #x3C #x60 #x80 #x3E #x00 ;;; re3
;;; #x00 #x90 #x40 #x3C #x81 #x40 #x80 #x40 ;;; mi3
;;; #x00 #x00 #xFF #x2F #x00) ;;; end of track
(defun conversion-midifile (signature tempo liste-couples)
(append (creer-header-chunk 1 4 96) (creer-track-chunk signature tempo liste-couples)))
(defun creer-header-chunk (format nbre-tracks division)
(append (mapcar #'char-code (list #\M #\T #\h #\d))
(list #x00 #x00 #x00 #x06) ;;; longueur = 6 octets
(list #x00 format) ;;; format 1
(list #x00 nbre-tracks) ;;; nbre de track chunks = 4
(list #x00 division))) ;;; "division" (nbre de "ticks"/pulsation) = 96
(defun creer-track-chunk (signature tempo liste-couples)
(append (mapcar #'char-code (list #\M #\T #\r #\k))
(let ((liste-octets (calcul-octets signature tempo liste-couples)))
(append (calcul-longueur (+ (length liste-octets) 4))
liste-octets
(list #x00 #xFF #x2F #x00))))) ;;; end of track
(defun calcul-octets (signature tempo liste-couples)
(append (calcul-signature signature) (calcul-tempo tempo) (calcul-liste-notes liste-couples)))
;;; Meta-evenement signature FF 58: a l'instant 0, par defaut 4/4
(defun calcul-signature (couple)
(list #x00 #xFF #x58 #x04 (car couple) (round (log (cadr couple) 2)) #x18 #x08))
;;; Meta-evenement tempo FF 51: a l'instant 0, duree noire en microsecondes sur 3 octets
(defun calcul-tempo (tempo)
(let ((n (/ (* 60 1000000) tempo)))
(list #x00 #xFF #x51 #x03
(logand (floor n #x10000) #xFF)
(logand (floor n #x100) #xFF)
(logand n #xFF))))
(defun calcul-liste-notes (liste-couples)
(if (null liste-couples) ()
(append (calcul-note (car liste-couples))
(calcul-liste-notes (cdr liste-couples)))))
(defun calcul-note (couple)
(append (list #x00 #x90 (car couple) #x3C) ;;; NoteOn
(append (calcul-deltatime (cadr couple))
(list #x80 (car couple) #x00)))) ;;; NoteOff
;;; Deltatime a longueur variable, sur octets de 7 bits avec 1 en tete
;;; sauf le dernier octet (0 en tete)
;;; EXEMPLE: 159 ticks = 128+16+8+4+2+1
;;; donne (1)000 0001 (0)001 1111 = #x81 #x1F
(defun calcul-deltatime (duree)
(let* ((new-octet (logand duree #x7F))
(liste-octets (list new-octet)))
(do ((d (floor duree #x80) (floor d #x80)))
((= d 0) liste-octets)
(setq new-octet (logand d #x7F)) ;;; 7 derniers bits
(setq liste-octets (cons (logior #x80 new-octet) liste-octets)))))
;;; Longueur du track chunk sur 4 octets:
(defun calcul-longueur (n)
(list (logand (floor n #x1000000) #xFF)
(logand (floor n #x10000) #xFF)
(logand (floor n #x100) #xFF)
(logand n #xFF)))