Score Mapping

Like Common Music and SmOKe, Score’s internal design is backend agnostic. This means that the representation of data is not tied to a single target music system. Users can use mapping functions to convert note lists generated by Score into a format that works with another system. This may be for use with other computer music systems but may also be used for visualisation or other purposes.

The following shows an example use of Score and Csound. It uses the gen-notes2 function to generate a note list from time 0 to 5.0 using 5 fields. The first field is a constant field that will always generate 1. The rest of the p-fields of the Csound score is generated using the values provided by within the score.mask package.

(def notes
  (gen-notes2 0 5.0
              1
              (gauss 0.5 0.1)
              (heap [0.1 0.2 0.4])
              (rand-range 0.1 0.25)
              (rand-item
                ["8.00" "8.03" "8.02"])))
(def csound-sco
  (format-sco notes))

(println notes)
(println csound-sco)

The results of printing the output are shown below:

;; output from (println notes)
[[1 0.0 0.1 0.1455446063675899 8.02]
 [1 0.07388877495229043 0.2 0.11487888605849467 8.00]
 [1 0.2684591839186033 0.4 0.12170487899979296 8.00]
 [1 1.0558572506209922 0.4 0.13304255988624555 8.03]
 [1 1.554791683668857 0.2 0.16436113185377213 8.00]
 [1 1.9392915161730429 0.1 0.11907587313489418 8.02]
 [1 2.3410899943560195 0.2 0.21996317376289015 8.03]
 [1 2.787924993057282 0.4 0.2119026696996974 8.00]
 [1 3.7580 770774079575 0.1 0.12327608647786711 8.00]
 [1 4.199933807980773 0.2 0.23620482696864334 8.00]]

;; output from (println csound-sco)
i1 0.0 0.1 0.1455446063675899 8.02
i1 0.07388877495229043 0.2 0.11487888605849467 8.00
i1 0.2684591839186033 0.4 0.12170487899979296 8.00
i1 1.0558572506209922 0.4 0.13304255988624555 8.03
i1 1.554791683668857 0.2 0.16436113185377213 8.00
i1 1.9392915161730429 0.1 0.11907587313489418 8.02
i1 2.3410899943560195 0.2 0.21996317376289015 8.03
i1 2.787924993057282 0.4 0.2119026696996974 8.00
i1 3.7580770774079575 0.1 0.12327608647786711 8.00
i1 4.199933807980773 0.2 0.23620482696864334 8.00

The first printout shows the results of running gen-notes2, which produces a Clojure list of lists. The second printout shows the result of using the format-sco function, provided by Score for formatting note lists into Csound SCO text format. The csound-sco text may then be further sent to a running Csound instance for live score performance or written to disk and later read by Csound as a SCO file.

At this time, Score only provides output mapping for Csound. However, Score’s generated note lists are usable as-is with Pink, as both systems are written in Clojure. In track1.clj, growing-line defines a note list using both features from Score and Pink. The code first uses two note lists generated using the gen-notes function that are concatenated together. This is then mapped over and the growl audio function is prepended as the first field of each note in the note list. The e argument given to gen-notes is itself a Pink audio function – the env function – that is wrapped using the !*! operator. The result is that for each note, the 6th field will be an instance of env used as the amplitude argument to the growl instrument.

;; from music-examples.track1 example file
(def growing-line
  (let [e (!*! env [0.0 400 0.11 5000])
        starts (range 0 1.8 (/ 1.0 3.0))
        amps (range 0.05 5 0.05)
        space (range 0.75 -1.0 -0.25)]
    (map #(into [growl] %)
         (concat
           (gen-notes starts 0.1 :G5 amps e 0.75 space)
           (gen-notes starts 0.1 :G3 amps e 0.75 space)
           ))))

From here, the growing-line note list is then reused as a part of a larger measured-score. convert-measured-score is used to prodcue to the total score, which is then mapped into Pink events using the sco->events function provided in the pink.simple namespace.

(defn apply-afunc-with-dur
  "Applies an afunc to given args within the context of a
  given duration. with-duration will bind the value of dur
  to the *duration* Pink context variable."
  [afunc dur & args]
  (with-duration (double dur)
    (apply!*! afunc args)))

(defn i
  "Csound style note events: audio-func, start, dur, & args."
  [afunc start dur & args]
  (apply event apply-afunc-with-dur start afunc dur args))

(defn sco->events
  "Converts Csound-style note list into a list of
  Pink Events."
  [notes]
  (map #(apply i %) notes))

The above shows the code for sco->events. Given a list of notes, sco->event maps an anonymous function that applies the i function to the values found in each note. The i function in turn applies the event function to each note, using apply-afunc-with-dur as the event’s function – the one that will fired by Pink’s event processor – with the given arguments. Finally, when apply-afunc-with-dur is called, it fires by processing the values found in the original note, applying the first field – the audio function – to the rest of the fields.

In the full track1.clj example, these Pink events are further passed to the add-audio-events function from pink.simple. This is a convenience function that wraps events with another event that uses the add-afunc function to attach audio functions to the root node of the engine. At runtime, when an event is fired, the nested event will generate an audio function and the top-level event will add it to the engine for processing.

The mapping of note lists is the technique by which the generated data from Score is connected to other systems. Score currently provides a mapping function for Csound and works out of the box with Pink, as shown in the example code. In the future, more mappings could be provided with Score, such as MIDI, OSC, and MusicXML. As the data generated from Score is plain Clojure list data, users can create their own mappings relatively simply.