(ns music) ;; an octave is an int, e.g. 4 ;; a pitch class is a keyword from :Abb to G## ;; a pitch is a [pitch class, octave] vector, e.g. [:C 4] is middle C ;; a duration is a rational number, e.g. 1/8 ;; a note has a duration, optionally a pitch, and optionally any other necessary keys (e.g. loudness) ;; {:duration 1/8 :pitch [:A# 3]} is a note. {:duration 1/4} is a note (really, a rest) ;; [:= noteA noteB] represents noteA played simultaneously with noteB ;; [:+ noteA noteB] represents noteA played followed by noteB ;; [:modify noteA control] annotates noteA with the control map ;; control maps have keys like ::tempo, ::transpose, ::instrument, ::phrase, ::player, ::keysig ;; primitives (def note1 {:duration 1/4, :pitch [:Eb 4]}) (def note2 {:duration 1/4, :pitch [:G 4]}) (def note3 {:duration 1/8, :pitch [:F 4]}) (def rest1 {:duration 1/8}) [:= note1 note2] ;; note1 simultaneous with note2 [:+ note1 note3] ;; note1 followed by note3 [:+ [:= note1 note2] note3] ;; note1 simultaneous with note2, followed by note3 [:modify note1 {::tempo 120}] ;; note1 modified to have tempo 120 [:modify [:= note1 note2] {::tempo 120 ::instrument "Dope Organ"}] ;; note1 simultaneous with note2 modified with tempo and instrument ;; can be nested to arbitrary complexity (def song [:= [:+ {:duration 1/4, :pitch [:D 3]} {:duration 1/4, :pitch [:E 3]} {:duration 1/4, :pitch [:F 3]}] [:+ {:duration 1/4, :pitch [:F 3]} {:duration 1/4, :pitch [:G 3]} {:duration 1/4, :pitch [:A 3]}]]) (defn note [duration pitch] {:duration duration, :pitch pitch}) (defn rest [duration] {:duration duration}) (defn tempo [tempo music] [:modify music {::tempo tempo}]) (defn transpose [abs-pitch music] [:modify music {::transpose abs-pitch}]) (defn instrument [instrument music] [:modify music {::instrument instrument}]) (defn phrase [phrase-attributes music] [:modify music {::phrase phrase-attributes}]) (defn player [player-name music] [:modify music {::player player-name}]) (defn keysig [pitch-class mode music] [:modify music {::keysig [pitch-class mode]}]) (defn pc-to-int [pitch-class] (case pitch-class :Cbb -2 :Cb -1 :C 0 :C# 1 :C## 2 :Dbb 0 :Db 1 :D 2 :D# 3 :D## 4 :Ebb 2 :Eb 3 :E 4 :E# 5 :E## 6 :Fbb 3 :Fb 4 :F 5 :F# 6 :F## 7 :Gbb 5 :Gb 6 :G 7 :G# 8 :G## 9 :Abb 7 :Ab 8 :A 9 :A# 10 :A## 11 :Bbb 9 :Bb 10 :B 11 :B# 12 :B## 13)) (defn abs-pitch "Returns the absolute pitch of a pitch class at an octave" ([pitch-class octave] (abs-pitch [pitch-class octave])) ([[pitch-class octave]] (+ (* 12 octave) (pc-to-int pitch-class)))) (defn pitch "Returns the pitch class and octave represented by the absolute pitch. In cases of enharmonic equivalence, returns the sharp rather than the flat." [abs-pitch] (let [scale [:C :C# :D :D# :E :F :F# :G :G# :A :A# :B] octave (quot abs-pitch 12) index (rem abs-pitch 12)] [(nth scale index) octave])) (defn trans [n [pitch-class octave]] (pitch (+ (abs-pitch pitch-class octave) n))) (defn line "Combines musics via the sequential (:+) operator" [[m & ms :as musics]] (if (nil? (seq musics)) (rest 0) [:+ m (line ms)])) (defn chord "Combines musics via the simultaneous (:=) operator" [[m & ms :as musics]] (if (nil? (seq musics)) (rest 0) [:= m (line ms)])) (defn max-pitch [[p & ps :as pitches]] (if (nil? (seq pitches)) (pitch 0) (let [max-ps (max-pitch ps)] (if (> (abs-pitch p) (abs-pitch max-ps)) p max-ps))))