SE701:April 11
Jump to navigation
Jump to search
;;; File: music.lisp ;;; Author: John Hamer ;;; Date: 11 April 2008 ;;; Purpose: Illustrate Common Lisp generic functions and classes (defclass music () ()) (defgeneric duration (music)) ;; A single note (defclass note (music) ((key :initarg :key :initform (error "Must specify the note key") :reader note-key) (duration :initarg :duration :initform (error "Must specify the note duration") :reader duration))) ;; Silence (defclass music-rest (music) ((duration :initarg :duration :initform 0 :reader duration))) ;; Sequential and parallel composition (defclass music-composite (music) ((terms :initarg :terms :reader music-terms))) (defclass par (music-composite) ()) (defclass seq (music-composite) ()) (defmethod duration ((m seq)) (reduce #'+ (music-terms m) :key #'duration)) (defmethod duration ((m par)) (reduce #'max (music-terms m) :key #'duration)) (defun seq (&rest terms) (make-instance 'seq :terms (collect-all-terms terms))) (defun par (&rest terms) (make-instance 'par :terms (collect-all-terms terms))) (defgeneric collect-terms (terms result)) (defmethod collect-terms ((terms list) result) (mapcar #'(lambda (term) (collect-terms term result)) terms)) (defmethod collect-terms ((terms vector) result) (loop for term across terms do (collect-terms term result))) (defmethod collect-terms ((term t) result) (vector-push-extend term result)) (defun collect-all-terms (terms) (let ((result (make-array 5 :fill-pointer 0 :adjustable t))) (collect-terms terms result) result)) ;;; Musical decorators (defclass music-decorator (music) ((term :initarg :term :initform (error "Must specify the music term") :reader music-term))) (defmethod duration ((m music-decorator)) (duration (music-term m))) ;; Tempo (defclass music-tempo (music-decorator) ((tempo :initarg :tempo :reader music-tempo))) (defmethod duration ((m music-tempo)) (* (duration (music-term m)) (music-tempo m))) (defun change-tempo (m r) (make-instance 'music-tempo :term m :tempo r)) ;; Transpose (defclass music-transpose (music-decorator) ((transpose :initarg :transpose :reader music-transpose))) (defun transpose (m d) (make-instance 'music-transpose :term m :transpose d)) ;; Instrument (defclass music-instrument (music-decorator) ((instrument :initarg :instrument :reader instrument))) (defun use-instrument (m i) (make-instance 'instr :term m :instrument i)) ;; Phrase (defclass music-phrase (music-decorator) ((phrase :initarg :phrase :reader music-phrase))) (defun phrasing (m p) (make-instance 'phrase :term m :phrase p)) ;;; Musical performance (defparameter *current-time* 0) (defparameter *current-tempo* 1) (defparameter *current-transpose* 0) (defparameter *current-instrument* "piano") (defparameter *current-volume* 100) (defclass Event () ((time :initarg :time :reader event-time) (key :initarg :key) (length :initarg :length) (instr :initarg :instr) (volume :initarg :volume))) (defmethod print-object ((e Event) stream) (with-slots (time key length instr volume) e (format stream "#[Event :time ~a :key ~a :length ~a :instr ~a :volume ~a]" time key length instr volume))) (defun mkEvent (time k l i v) (make-instance 'Event :time time :key k :length l :instr i :volume v)) (defun do-perform (music) (let ((events (make-array 0 :adjustable t :fill-pointer t))) (perform music 0 events) (sort events #< :key #'event-time) events)) (defgeneric perform (music time events)) (defmethod perform ((m note) time events) (with-slots (key duration) m (vector-push-extend (mkEvent time (+ key *current-transpose*) (* duration *current-tempo*) *current-instrument* *current-volume*) events) duration)) (defmethod perform ((m music-rest) time events) (duration m)) (defmethod perform ((m seq) time events) (let ((dur 0)) (loop for mt across (music-terms m) do (incf dur (perform mt (+ time dur) events))) dur)) (defmethod perform ((m par) time events) (let ((dur 0)) (loop for e across (music-terms m) do (setf dur (max dur (perform e time events)))) dur)) (defmethod perform ((m music-tempo) time events) (let ((*current-tempo* (* *current-tempo* (music-tempo m)))) (perform (music-term m) time events))) (defmethod perform ((m music-instrument) time events) (let ((*current-instrument* (instrument m))) (perform (music-term m) time events))) ;;; Retro (defgeneric retro (music) (:documentation "MUSIC played backwards")) (defmethod retro ((m music)) m) (defmethod retro ((p par)) (par (mapcar #'retro (music-terms p)))) (defmethod retro ((s seq)) (seq (nreverse (mapcar #'retro (music-terms s))))) (defmethod retro ((m music-tempo)) (change-tempo (retro (music-term m)) (music-tempo m))) (defmethod retro ((m music-transpose)) (transpose (retro (music-term m)) (music-transpose m))) (defmethod retro ((m music-instrument)) (use-instrument (retro (music-term m)) (instrument m))) (defmethod retro ((m music-phrase)) (phrasing (retro (music-term m)) (music-phrase m))) ;;; Reading and writing ;; Print using initialize-instance syntax: "#[type init-args]" (defun read-music-object (stream char n) (declare (ignore char n)) (apply #'make-instance (read stream t) (read-delimited-list #\] stream t))) (set-syntax-from-char #\] #\)) (set-dispatch-macro-character #\# #\[ #'read-music-object) (defmethod print-object ((obj note) stream) (with-slots (key duration) obj (format stream "#[note :key ~a :duration ~a]" key duration))) (defmethod print-object ((obj music-rest) stream) (with-slots (duration) obj (format stream "#[music-rest :duration ~a]" duration))) (defmethod print-object ((obj music-composite) stream) (with-slots (terms) obj (format stream "#[~a :terms ~a]" (type-of obj) terms))) (defgeneric print-decoration (music-decorator stream)) (defmethod print-object ((obj music-decorator) stream) (format stream "#[~a :term ~a " (type-of obj) (music-term obj)) (print-decoration obj stream) (format stream "]")) (defmethod print-decoration ((obj music-tempo) stream) (format stream ":tempo ~a" (music-tempo obj))) (defmethod print-decoration ((obj music-transpose) stream) (format stream ":transpose ~a" (music-transpose obj))) (defmethod print-decoration ((obj music-instrument) stream) (format stream ":instr ~a" (instrument obj))) (defmethod print-decoration ((obj music-phrase) stream) (format stream ":phrase ~a" (music-phrase obj))) (defun key-code (name) (ccase name (:A 0) ((:As :Bf) 1) (:B 2) (:C 3) ((:Cs :Df) 4) (:D 5) ((:Ds :Ef) 6) (:E 7) (:F 8) ((:Fs :Gf) 9) (:G 10) ((:Gs :Af) 11))) (defun note (name octave duration) (make-instance 'note :key (+ (* octave 12) (key-code name)) :duration duration)) (defun repeat (n mus) (seq (make-array n :initial-element mus))) ;;; Example (defvar *three-blind-mice* (let* ((qn 1/4) (en 1/8) (en3 3/8) (en6 6/8) (qnr (make-instance 'music-rest :duration qn)) (t1 (seq (note :D 4 en3) (note :C 4 en3) (note :Bf 3 en6))) (t2 (seq (note :F 4 en3) (note :Ef 4 qn) (note :Ef 4 en))) (t3 (seq (note :F 4 en) (note :Bf 4 qn) (note :Bf 4 en) (note :A 4 en) (note :G 4 en) (note :A 4 en) (note :Bf 4 qn) (note :F 4 en) (note :F 4 qn)))) (seq (repeat 2 t1) t2 (note :D 4 en6) t2 (note :D 4 en3) qnr (repeat 3 t3) (note :Ef 4 en) t1)))