diff --git a/src/performance.clj b/src/performance.clj index 9d8e6c9..64ffaaa 100644 --- a/src/performance.clj +++ b/src/performance.clj @@ -1,16 +1,82 @@ (ns performance (:require [clojure.core.match :refer [match]])) +;; A note attribute is a modifier or descriptor attached to a note +;; It goes in the note map as a map in the :attrs key +;; Here's an example of a note with attributes: +(def note-with-attributes + {:pitch [:E 4] + :attrs {:volume 127 ;; 0 - 127 + :fingering 4 ;; ??? + :dynamics "pianissimo" ;; maybe, not actually sure what string should go here + :params []}}) ;; an array of doubles ??? + +;; A phrase attribute is attached to the :phrase key in a :modify operation +;; It is a map with any of keys :dynamic, :tempo, :articulation, :ornament. +;; Each value is a vector where the first value is a type descriptor and the +;; second is a numeric value, if applicable. See p. 144 for all possible values +(def some-phrase-attributes + {:dynamic [:crescendo 30] + :tempo [:accelerando 3] + :articulation [:staccato 10] + :ornament [:trill]}) + ;; A player is an entity that knows how to interpret music in a certain ways ;; I need to flesh most of this out -- see section 8.3 on page 141 -(def a-player - {:name "player1" ;; player name +(declare default-play-note) +(declare default-interp-phrase) +(declare perf) +(def default-player + {:name "Default" ;; player name :play-note - (fn [context note] ()) ;; function to play a note with a context, returns Performance + default-play-note ;; function to play a note with a context, returns Performance :interp-phrase - (fn [p-map context attrs music]) ;; function to interpret a Music value, returns performance + default-interp-phrase ;; function to interpret a phrase, returns performance :notate (fn [context note] ())}) ;; function to transcribe a note - not implemented in Euterpea +(def default-player-map {"Default" default-player}) + +(defn default-play-note + "Turns a note into an event, interpreting the :volume attribute" + [context note] + (let [{:keys [time instrument duration-t pitch-offset volume]} context + {:keys [duration pitch attrs]} note + event {:time time + :instrument instrument + :duration (* duration duration-t) + :volume volume + :pitch (+ (music/abs-pitch pitch) pitch-offset) + :params []}] ;; still don't know what params is for + (reduce ;; this could be an elegant way to handle :modify too + (fn [evt attr] + (match attr + [:volume v] (assoc evt :volume v) + [:params pms] (assoc evt :params pms) + _ evt)) + event (seq attrs)))) + +(defn default-interp-phrase + "Interprets a phrase, accounting for accents, staccato, and legato" + [player-map context attrs music] + (let [[performance duration] (perf player-map context music) + interpreted (reduce + (fn [pf attr] + (match attr + [:dynamic [:accent n]] (map + #(assoc % :volume (Math/round + (* n (:volume %)))) + pf) + [:articulation [:staccato n]] (map #(assoc % :duration + (* n (:duration %))) + pf) + [:articulation [:legato n]] (map #(assoc % :duration + (* n (:duration %))) + pf) + _ pf)) + performance (seq attrs))] + [interpreted duration])) + + ;; A performance is a data structure that describes the act ;; of performing a piece of music. It is a vector of events, ;; where each event describes the playing of a single note @@ -28,14 +94,14 @@ ;; changing tempo, key signature, tempo, etc. (def my-context {:time 0 ;; time at which the performance starts in seconds - :player a-player ;; player currently interpreting the music + :player default-player ;; player currently interpreting the music :instrument "Cello" ;; instrument currently playing (?) - :duration-t 2 ;; the duration in seconds of a whole note + :duration-t 4 ;; the duration in seconds of a whole note -- 4 == 60 bpm :pitch-offset 0 ;; a pitch offset -- number of semitones to transpose notes up or down :volume 50 ;; volume of performance :key [:C :major]}) ;; current key signature -(defn merge-perfs +(defn merge-perfs ;; TODO what if p1 and p2 are single notes? "Merges two performances" [p1 p2] (if (nil? (seq p1)) @@ -48,12 +114,6 @@ (into [e1] (merge-perfs (rest p1) p2)) (into [e2] (merge-perfs p1 (rest p2)))))))) -(defn assoc-if-present - [cmap context control key val-func] - (if (control cmap) - (assoc context key (val-func (control cmap))) - context)) - (declare modify) (defn perf @@ -62,11 +122,10 @@ (let [{:keys [player duration-t]} context] (match music {:duration duration - :pitch pitch} (apply (:play-note player) - context - duration - pitch - (* duration duration-t)) + :pitch pitch} [((:play-note player) + context + music) + (* duration duration-t)] {:duration duration} [[] (* duration duration-t)] [:+ & ms] (reduce (fn [[events-acc dur-acc] m] (let [[events dur] (perf player-map @@ -89,8 +148,6 @@ context m)))) -(def default-player-map {}) ;; TODO - (defn perform "Convert a music value to a performance value" ([player-map context music] @@ -99,21 +156,21 @@ (perform default-player-map context music))) (defn modify - "Modifies the context of a performance, returns new performance" + "Modifies the context of a performance, returns a new performance" [control player-map context music] - (let [assoc-func (partial assoc-if-present control) + (let [phrase (:phrase control) new-context - (-> context - (assoc-func :tempo :duration-t - (fn [r] (/ (:duration-t context) r))) - (assoc-func :transpose :pitch-offset - (fn [p] (+ (:pitch-offset context) p))) - (assoc-func :instrument :instrument identity) - (assoc-func :key :key identity) ;; a key looks like [:C :major] - (assoc-func :player :player - (fn [name] (name player-map))))] - (if (:phrase control) - ((:interp-phrase (:player new-context)) player-map new-context (:phrase control)) + (reduce (fn [cxt c] + (match c + [:tempo r] (assoc cxt :duration-t (/ (:duration-t cxt) r)) + [:transpose p] (assoc cxt :pitch-offset (+ (:pitch-offset cxt) p)) + [:instrument i] (assoc cxt :instrument i) + [:key key] (assoc cxt :key key) + [:player name] (assoc cxt :player (get player-map name)) + _ cxt)) + context (seq control))] + (if phrase + ((:interp-phrase (:player new-context)) player-map new-context phrase music) (perf player-map new-context music)))) (defn metro