clojure-school-of-music/src/music.clj

126 lines
3.9 KiB
Clojure
Raw Normal View History

2018-08-06 02:52:54 +00:00
(ns music
(:require [clojure.core.match :refer [match]]))
2018-07-16 22:39:51 +00:00
;; 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)
2018-07-17 03:12:32 +00:00
;; [:= 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
2018-07-17 03:12:32 +00:00
;; 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 {::tempo 120} note1] ;; note1 modified to have tempo 120
[:modify {::tempo 120
::instrument "Dope Organ"}
[:= note1 note2]] ;; 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 {::tempo tempo} music])
(defn transpose [abs-pitch music]
[:modify {::transpose abs-pitch} music])
(defn instrument [instrument music]
[:modify {::instrument instrument} music])
(defn phrase [phrase-attributes music]
[:modify {::phrase phrase-attributes} music])
(defn player [player-name music]
[:modify {::player player-name} music])
(defn keysig [pitch-class mode music]
[:modify {::keysig [pitch-class mode]} music])
(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]
2018-07-20 00:10:03 +00:00
(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
2018-07-20 02:41:53 +00:00
"Combines musics via the sequential (:+) operator"
[[m & ms :as musics]]
(if (nil? (seq musics))
2018-07-20 02:41:53 +00:00
(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))))
2018-08-06 02:52:54 +00:00
(defn dur
"Computes the duration of the music value"
[music]
(match music
{:duration d} d
[:+ m & ms] (+ (dur m) (dur (into [:+] ms)))
[:+] 0
[:= & ms] (apply max (map dur ms))
[:=] 0
[:modify {:tempo r} m] (/ (dur m) r)
[:modify _ m] (dur m)))