article_3

[ 2016 ]

by *Yann Ics*

http://experimental.mus-ics.net

:Cite this articleYann Ics, Writing score for SuperCollider, 2016Date retrieved: 27/05/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 objetcs destined to be used in *SuperCollider*^{1)} 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 with the analytic procedure of *enkode*. In this way, it is possible to distinguish this score with the formated scores describe in *IMP #0* (*op. cit.* p. 2).

The score is formated 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:
- the 'tempo' expressed by the perception time smear in millisecond
- the unit time relative to the 'tempo'
- the number of repetition (can be adjust if needed in
*SuperCollider*context)

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

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

This is done with the following script *PRAAT*^{2)}.

form Get values sentence soundfile ... sentence textgrid ... endform 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 Remove endfor select all Remove

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 *gnuplot*^{3)}.

#!/bin/bash infile=$1 l=`cat $infile | wc -l` cent="00" 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 i=1 while IFS= read -r line do 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

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

- First, according 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 other 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).

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 weigth 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 1 | a_{0} | b_{0} | c_{0} |

Line 2 | a_{1} | b_{1} | c_{1} |

Line 3 | a_{2} | b_{2} | c_{2} |

… | … | … | … |

Line n | a_{n-1} | b_{n-1} | c_{n-1} |

Line n+1 | a_{n} | b_{n} | c_{n} |

a_{0} = bin width (5000/116)

b_{0} = *enkode* version

c_{0} = timestamp

a = frequency (from a_{1} to a_{n})

b = 'presence' (mean energy of the signal *en-temps*)

c = energy ('formal' energy *hors-temps*)

*n* = the number of discrimination of the relative pitch

;; 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) 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) nil (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) nil (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)) (analisis-contrasts-level.1 (mapcar #'(lambda (x y) (mapcar #'(lambda (z) (if (equalp x z) y 'nil)) sequence)) elements order))) (flat (mat-trans analisis-contrasts-level.1)))) (defun contrasts-all-lev (sequence) (let* ((counter-sequence (arithm-ser (length sequence) 1 -1)) (contrasts-lev.1-for-all-level (mapcar #'(lambda (x) (contrasts-lev.1 (last sequence x))) counter-sequence))) (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))) (distances (mapcar #'(lambda (x) (x->dx x)) (contrasts-all-lev sequence-whit-silence-start-end))) (weights (mapcar #'(lambda (x) (apply '+ x)) (contrasts-all-lev sequence-whit-silence-start-end))) (contrasts-lev.1*weights (mapcar #'(lambda (x y) (m* y x)) distances weights)) (contrasts-all-lev*weights (reverse (mapcar #'(lambda (xx) (apply '+ xx)) (mat-trans (mapcar #'(lambda (x) (reverse x)) contrasts-lev.1*weights)))))) (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))) absolute-value2)) ;; 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 band width of the spectrum." (let ((ser (all-ser))) (loop for spec in spectrum collect (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) 0.0 (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 formated score, and an article especially dedicated to the present work and its musical application(s) will be written in the next few years.

article_3.txt · Dernière modification: 2018/03/05 22:17 (modification externe)