Outils pour utilisateurs

Outils du site


[ 2016 ]

by Yann Ics



Cite this article:
Yann Ics, Writing score for SuperCollider, 2016
Date retrieved: 14/11/18
Permanent link: http://experimental.mus-ics.net/wiki/doku.php?id=article_3


This article represents a straight continuation of enkode, melody to tone and IMP #0. The aim of this article is to describe two objects destined to be used in SuperCollider1) by using the command line enkode in order to realize a synthetic music in a structural and formal context. These objects are the result of analytic procedure realized with the executable enkode and interpreted with the option --score and the option --M2T.


The score will be identified in SuperCollider by the number of lines minus one, that is to say, the odd number 5, which correspond to the 5 parameters generated by the analytic procedure of enkode. In this way, it is possible to distinguish this score with the formatted scores describe in IMP #0 (op. cit. p. 2).

The score is formatted as follow:

  • Line 1: durations
  • Line 2: dynamics
  • Line 3: relative pitches
  • Line 4: brightness
  • Line 5: bass intensity
  • Line 6 (the last) has 3 arguments:
    1. the 'tempo' expressed by the perception time smear in a millisecond
    2. the unit time relative to the 'tempo'
    3. the number of repetition (can be adjusted if needed in SuperCollider context)

The length of each line (except the last) is the number of events of the analyzed sequence. Thus each column of the five first lines is an event dataset.


Spectrum slice

To introduce the 'M2T' analysis procedure, we need to extract the spectrum slice for each event.

This is done with the following script PRAAT2).

form Get values
    sentence soundfile ...
    sentence textgrid ...

Read from file... 'soundfile$'
current_sound$ = selected$ ("Sound") 
Read from file... 'textgrid$'
current_textgrid$ = selected$ ("TextGrid")

filedelete 'defaultDirectory$'/ofp

select TextGrid 'current_textgrid$'
n = Get number of intervals: 1
    	for nn from 1 to n
	select Sound 'current_sound$'
	plus TextGrid 'current_textgrid$'
	Extract intervals where: 1, "no", "is equal to", "'nn'"

        	To Spectrogram: 0.005, 5000, 0.002, 20, "Gaussian"
		To Spectrum (slice): 0	
		List: "no", "no", "no", "no", "no", "yes"

                appendFile: "ofp", info$ ( )
                select Sound 'current_sound$'_'nn'_1
                plus Spectrogram 'current_sound$'_'nn'_1
                plus Spectrum 'current_sound$'_'nn'_1
select all

This is done implicitly but it is possible to get these values by adding the argument +spectrum at the command line enkode.

The following script bash allows to plot the spectrum file with gnuplot3).

l=`cat $infile | wc -l`
echo "set terminal pngcairo size 300, $l$cent" > gnuplot.in
echo "set output 'spectrumPlotting.png'" >> gnuplot.in
echo "set multiplot layout $l,1" >> gnuplot.in
echo "set key font \",10\"; set format x \"\"; unset  ytics" >> gnuplot.in
while IFS= read -r line
    echo $line | tr ' ' '\012' > tmppp.$i
    echo "plot \"tmppp.$i\" with lines title \"event $i\"" >> gnuplot.in
    i=$((i + 1))
done < $infile
echo "unset multiplot; set output" >> gnuplot.in
gnuplot gnuplot.in
rm tmppp.*
rm gnuplot.in

Sorting melody

The ranking of the relative pitch discrimination is realized from the spectrum slice applied to every single event.

  • First, according to the bandwidth of the spectrum (0 - 5000 Hz) and the bin width (5000/116), we list all the possible 'harmonic' series by bin (see function all-ser).
  • Then, we collect the greater value defined by the summation of the energy of each bin width involved divided by the number of bin width involved for each event (see function mean-sum).
  • Then, the summation of all harmonic series selected, by column (in another word by bin) is done with the function summation-hors-tps and we retain the n first greater value in order to get the 'sorting melody' (n is defined by the number of discrimination of the relative pitch).

Energy profile

The relative pitch index is associated with the sorting frequencies of the sorting melody. Thus, following the sequence 'en-temps', the energy profile (see energy-prof-morph-analysis) is computed from the weight of the 'sorting melody' multiply by the duration.
Then the energy of each relative pitch is listed in order to get the average. These energetic average values can be qualified as 'hors-temps' with its associated relative pitch.

The result is formated as follow:

Col 1 Col 2 Col 3
Line 1a0b0c0
Line 2a1b1c1
Line 3a2b2c2
Line nan-1bn-1cn-1
Line n+1anbncn

a0 = bin width (5000/116)
b0 = enkode version
c0 = timestamp

a = frequency (from a1 to an)
b = 'presence' (mean energy of the signal en-temps)
c = energy ('formal' energy hors-temps)
n = the number of discrimination of the relative pitch

Lisp code

;; read loudness spectrum file
(defun read-text-lines (file)
  (with-open-file (in-stream file
			     :direction :input
                             :element-type 'character)
    (loop with length = (file-length in-stream)
       while (< (file-position in-stream) length)
       collect (read-line in-stream))))
(defun string-to-list (string)
  "Returns a list of the data items represented in the given list."
  (let ((the-list nil) ;; we'll build the list of data items here
        (end-marker (gensym))) ;; a unique value to designate "done"
    (loop (multiple-value-bind (returned-value end-position)
	      (read-from-string string nil end-marker)
            (when (eq returned-value end-marker)
              (return the-list))
            ;; if not done, add the read thing to the list
            (setq the-list 
                  (append the-list (list returned-value)))
            ;; and chop the read characters off of the string
            (setq string (subseq string end-position))))))
;; energy-prof-morph-analysis from PWGL morphologie library
(defun arithm-ser (begin end step)
  (if (plusp step)
      (loop for i from begin to end by step collect i)
      (reverse (loop for i from end to begin by (abs step) collect i))))
(defun flat (lst)
  (if (endp lst)
      (if (atom (car lst))
	  (append (list (car lst)) (flat (cdr lst)))
	  (append (flat (car lst)) (flat (cdr lst))))))
;; http://stackoverflow.com/questions/39943232
(defun cars (matrix)
  "Return a list with all the cars of the lists in matrix"
  (if (null matrix)
      (cons (car (car matrix)) (cars (cdr matrix)))))
(defun cdrs (matrix)
  "Return a list with all the cdrs of the lists in matrix"
  (if (null matrix)
      (cons (cdr (car matrix)) (cdrs (cdr matrix)))))
(defun transpose (matrix)
  "Transpose matrix"
  (cond ((null matrix) nil)
        ((null (car matrix)) nil)
        (t (cons (cars matrix) (transpose (cdrs matrix))))))
(defun mat-trans (lst)
  (mapcar #'(lambda (x) (remove nil x)) (transpose lst)))
(defun x->dx (lst)
  (loop for x in lst
     for y in (rest lst)
     collect (- y x)))
(defmethod m* ((a number) (b list))
  (mapcar #'(lambda (x) (m* x a)) b))
(defmethod m* ((a list) (b number))
  (m* b a))
(defmethod m* ((a number) (b number))
  (* a b))
(defmethod m* ((a list) (b list))
  (mapcar #'m* a b))
(defun contrasts-lev.1 (sequence)
  (let* ((elements (reverse (remove-duplicates (reverse sequence) :test #'equalp))) 
         (order (arithm-ser 1 (length elements) 1))
          (mapcar #'(lambda (x y)
                      (mapcar #'(lambda (z) (if (equalp x z) y 'nil)) sequence))
    (flat (mat-trans analisis-contrasts-level.1))))
(defun contrasts-all-lev (sequence)
  (let* ((counter-sequence (arithm-ser (length sequence) 1 -1))
	  (mapcar #'(lambda (x)
		      (contrasts-lev.1 (last sequence x)))
    (butlast contrasts-lev.1-for-all-level)))
(defun new-old-analysis (sequence)
  (let* ((sequence-whit-silence-start-end
	  (append (list 'symbol-silence-start) sequence
		  (list 'symbol-silence-end)))
	  (mapcar #'(lambda (x) (x->dx x))
		  (contrasts-all-lev sequence-whit-silence-start-end)))
	  (mapcar #'(lambda (x) (apply '+ x))
		  (contrasts-all-lev sequence-whit-silence-start-end)))
	  (mapcar #'(lambda (x y) (m* y x)) distances weights))
	  (reverse (mapcar #'(lambda (xx) (apply '+ xx))
			   (mat-trans (mapcar
				       #'(lambda (x) (reverse x))
    (butlast contrasts-all-lev*weights)))
(defun energy-prof-morph-analysis (sequence)
  (let* ((analysis-old-new (cons '0 (new-old-analysis sequence)))
	 (absolute-value (mapcar #'abs analysis-old-new))
	 (local-derivative (x->dx absolute-value))
	 (absolute-value2 (mapcar #'abs local-derivative)))
;; sort-melody from PWGL M2T library
(defvar *f-range* 5000)
(defvar *f-bin* 116)
(defparameter *bw* (/ *f-range* *f-bin*))
(defun get-nfirst-sort-with-index (lst &optional (nfirst 1))
  (subseq (sort (loop for x in lst for i from 0 collect (list x i)) #'> :key #'car) 0 (min (length lst) nfirst)))
(defun all-ser (&optional (f-range *f-range*) (f-bin *f-bin*))
  (let ((init-ser (arithm-ser (/ f-range f-bin) f-range (/ f-range f-bin))))
    (loop for i in init-ser collect (mapcar #'(lambda (x) (cadr (assoc x (transpose (list init-ser (arithm-ser 0 (1- f-bin) 1)))))) (arithm-ser i f-range i)))))
(defun mean-sum (spectrum)
  "Summation of all harmonics with spectrum values divided by the number of harmonics for all possible harmonic series (all-ser) in the range of the bandwidth of the spectrum."
  (let ((ser (all-ser)))
       for spec in spectrum
	 (nth (cadar (get-nfirst-sort-with-index (loop for x in ser collect (/ (loop for y in x sum (nth y spec)) (length x))))) ser))))
(defun replace-a (new n lst)
  (mapcar #'(lambda (a) (if (= (setq n (1- n)) -1) new a)) lst))
(defun fill-lst-a (ilst alst &optional (len *f-bin*))
  (let ((res (make-list len :initial-element 0)))
    (loop for aaa in ilst do (setf res (replace-a (/ (nth aaa alst) (length ilst)) aaa res))) res))
(defun summation-hors-tps (spectrum)
  (let ((tmp (mean-sum spectrum)))
    (mapcar #'(lambda (x) (apply #'+ x))
	    (mat-trans (loop for i in tmp for j in spectrum collect (fill-lst-a i j))))))
(defun sort-melody (spectrum relp &optional (bw *bw*))
  (mapcar #'(lambda (x) (list (* (+ 1.0 (cadr x)) bw) (car x))) (get-nfirst-sort-with-index (summation-hors-tps spectrum) (apply #'max relp))))
;; compute energy profile
(defun energy-profile (sm relp dur)
  (energy-prof-morph-analysis (loop for irel in relp for idur in dur collect (* idur (cadr (assoc irel sm))))))
(defun mean (lst)
  (if (null lst)
      (float (/ (reduce #'+ lst) (length lst)))))
;;;; written in the script shell
;; (defparameter *spectrum* (mapcar #'string-to-list (read-text-lines <path_to_spectrum_file>)))
;; (defparameter *data* (mapcar #'string-to-list (read-text-lines <path_to_num_file>)))
;; (defparameter *version* <version_of_enkode>)
;; (defparameter *timestamp* "<timestamp>") ;; refers to the zip archive if overwritten.
(defparameter *relative-pitch* (mapcar #'caddr *data*))
(defparameter *duration* (mapcar #'car *data*))
(defparameter *sm* (loop for n in (sort (sort-melody *spectrum* *relative-pitch*) #'< :key #'car) for i from 1 collect (list i (cadr n))))
(defun mk-m2t-file (str-dir lst) 
  (with-open-file (stream (make-pathname :directory (pathname-directory str-dir)
					 :name (pathname-name str-dir)
					 :type (pathname-type str-dir))
			  :direction :output
			  :if-exists :supersede
			  :if-does-not-exist :create)
    (loop for i in lst do (format stream "~{~D ~} ~&" i))))
;;;; Written in the script shell.
;;;; The first line is written in case of failure during the dynamic profile computation. 
;; (mk-m2t-file <path_of_m2t_file> (cons (list (float *bw*) *version* (format nil "~A" *timestamp*)) (mapcar #'(lambda (x) (list (* (+ 1.0 (car x)) *bw*) (cadr x) (mean (mapcar #'cadr (loop for i in (mat-trans (list *relative-pitch* (make-list (length *relative-pitch*) :initial-element 0.0))) when (= (car i) (car x)) collect i))))) *sm*)))
;;;; This line will run some time and will give up (done with cmd timeout).
;; (let ((tmp (energy-profile *sm* *relative-pitch* *duration*)))
;;   (mk-m2t-file <path_of_m2t_file> (cons (list (float *bw*) *version* (format nil "~A" *timestamp*)) (mapcar #'(lambda (x) (list (* (+ 1.0 (car x)) *bw*) (cadr x) (mean (mapcar #'cadr (loop for i in (mat-trans (list *relative-pitch* tmp)) when (= (car i) (car x)) collect i))))) *sm*))))


These results are destined to be read and interpreted into the SuperCollider context – or other – in order to feed algorithmic processes from sound file converted by enkode.

One of the possible algorithmic processes is described in IMP #0 (op. cit.) notably concerning the formatted score, and an article especially dedicated to the present work and its musical application(s) will be written in the next few years.

1) SuperCollider is a programming language for real time audio synthesis and algorithmic composition.
2) PRAAT is a free software for the analysis of speech in phonetics.
3) Gnuplot is a command line program that can generate two and three dimensional plots of functions, data, and data fits.
article_3.txt · Dernière modification: 2018/08/20 16:06 (modification externe)