Implement performance (broken)

This commit is contained in:
Jeremy Dormitzer 2018-08-14 08:38:56 -04:00
parent 1d12d95542
commit 489741d3ed

View File

@ -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)
:pitch pitch} [((:play-note player)
context
duration
pitch
(* duration duration-t))
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